summaryrefslogtreecommitdiffstats
path: root/subversion
diff options
context:
space:
mode:
Diffstat (limited to 'subversion')
-rw-r--r--subversion/include/mod_authz_svn.h61
-rw-r--r--subversion/include/mod_dav_svn.h99
-rw-r--r--subversion/include/private/README4
-rw-r--r--subversion/include/private/ra_svn_sasl.h86
-rw-r--r--subversion/include/private/svn_adler32.h52
-rw-r--r--subversion/include/private/svn_atomic.h123
-rw-r--r--subversion/include/private/svn_auth_private.h220
-rw-r--r--subversion/include/private/svn_cache.h486
-rw-r--r--subversion/include/private/svn_client_private.h299
-rw-r--r--subversion/include/private/svn_cmdline_private.h228
-rw-r--r--subversion/include/private/svn_dav_protocol.h68
-rw-r--r--subversion/include/private/svn_debug.h107
-rw-r--r--subversion/include/private/svn_delta_private.h128
-rw-r--r--subversion/include/private/svn_dep_compat.h184
-rw-r--r--subversion/include/private/svn_diff_private.h115
-rw-r--r--subversion/include/private/svn_diff_tree.h357
-rw-r--r--subversion/include/private/svn_doxygen.h32
-rw-r--r--subversion/include/private/svn_editor.h1194
-rw-r--r--subversion/include/private/svn_eol_private.h93
-rw-r--r--subversion/include/private/svn_error_private.h54
-rw-r--r--subversion/include/private/svn_fs_private.h189
-rw-r--r--subversion/include/private/svn_fs_util.h217
-rw-r--r--subversion/include/private/svn_fspath.h175
-rw-r--r--subversion/include/private/svn_io_private.h99
-rw-r--r--subversion/include/private/svn_log.h260
-rw-r--r--subversion/include/private/svn_magic.h55
-rw-r--r--subversion/include/private/svn_mergeinfo_private.h270
-rw-r--r--subversion/include/private/svn_mutex.h117
-rw-r--r--subversion/include/private/svn_named_atomic.h162
-rw-r--r--subversion/include/private/svn_opt_private.h156
-rw-r--r--subversion/include/private/svn_pseudo_md5.h83
-rw-r--r--subversion/include/private/svn_ra_private.h280
-rw-r--r--subversion/include/private/svn_ra_svn_private.h826
-rw-r--r--subversion/include/private/svn_repos_private.h121
-rw-r--r--subversion/include/private/svn_skel.h236
-rw-r--r--subversion/include/private/svn_sqlite.h519
-rw-r--r--subversion/include/private/svn_string_private.h222
-rw-r--r--subversion/include/private/svn_subr_private.h340
-rw-r--r--subversion/include/private/svn_temp_serializer.h207
-rw-r--r--subversion/include/private/svn_token.h98
-rw-r--r--subversion/include/private/svn_utf_private.h87
-rw-r--r--subversion/include/private/svn_wc_private.h1847
-rw-r--r--subversion/include/svn_auth.h1282
-rw-r--r--subversion/include/svn_base64.h123
-rw-r--r--subversion/include/svn_cache_config.h90
-rw-r--r--subversion/include/svn_checksum.h278
-rw-r--r--subversion/include/svn_client.h6475
-rw-r--r--subversion/include/svn_cmdline.h376
-rw-r--r--subversion/include/svn_compat.h104
-rw-r--r--subversion/include/svn_config.h808
-rw-r--r--subversion/include/svn_ctype.h196
-rw-r--r--subversion/include/svn_dav.h398
-rw-r--r--subversion/include/svn_delta.h1367
-rw-r--r--subversion/include/svn_diff.h1118
-rw-r--r--subversion/include/svn_dirent_uri.h805
-rw-r--r--subversion/include/svn_dso.h99
-rw-r--r--subversion/include/svn_error.h662
-rw-r--r--subversion/include/svn_error_codes.h1521
-rw-r--r--subversion/include/svn_fs.h2530
-rw-r--r--subversion/include/svn_hash.h265
-rw-r--r--subversion/include/svn_io.h2282
-rw-r--r--subversion/include/svn_iter.h139
-rw-r--r--subversion/include/svn_md5.h91
-rw-r--r--subversion/include/svn_mergeinfo.h612
-rw-r--r--subversion/include/svn_nls.h56
-rw-r--r--subversion/include/svn_opt.h779
-rw-r--r--subversion/include/svn_path.h734
-rw-r--r--subversion/include/svn_pools.h114
-rw-r--r--subversion/include/svn_props.h714
-rw-r--r--subversion/include/svn_quoprint.h77
-rw-r--r--subversion/include/svn_ra.h2468
-rw-r--r--subversion/include/svn_ra_svn.h668
-rw-r--r--subversion/include/svn_repos.h3406
-rw-r--r--subversion/include/svn_sorts.h223
-rw-r--r--subversion/include/svn_string.h577
-rw-r--r--subversion/include/svn_subst.h708
-rw-r--r--subversion/include/svn_time.h94
-rw-r--r--subversion/include/svn_types.h1287
-rw-r--r--subversion/include/svn_user.h56
-rw-r--r--subversion/include/svn_utf.h252
-rw-r--r--subversion/include/svn_version.h411
-rw-r--r--subversion/include/svn_wc.h8182
-rw-r--r--subversion/include/svn_xml.h381
-rw-r--r--subversion/libsvn_auth_gnome_keyring/gnome_keyring.c517
-rw-r--r--subversion/libsvn_auth_gnome_keyring/version.c35
-rw-r--r--subversion/libsvn_auth_kwallet/kwallet.cpp458
-rw-r--r--subversion/libsvn_auth_kwallet/version.c35
-rw-r--r--subversion/libsvn_client/add.c1326
-rw-r--r--subversion/libsvn_client/blame.c837
-rw-r--r--subversion/libsvn_client/cat.c308
-rw-r--r--subversion/libsvn_client/changelist.c144
-rw-r--r--subversion/libsvn_client/checkout.c198
-rw-r--r--subversion/libsvn_client/cleanup.c63
-rw-r--r--subversion/libsvn_client/client.h1124
-rw-r--r--subversion/libsvn_client/cmdline.c363
-rw-r--r--subversion/libsvn_client/commit.c1031
-rw-r--r--subversion/libsvn_client/commit_util.c1981
-rw-r--r--subversion/libsvn_client/compat_providers.c136
-rw-r--r--subversion/libsvn_client/copy.c2422
-rw-r--r--subversion/libsvn_client/copy_foreign.c571
-rw-r--r--subversion/libsvn_client/ctx.c112
-rw-r--r--subversion/libsvn_client/delete.c595
-rw-r--r--subversion/libsvn_client/deprecated.c2966
-rw-r--r--subversion/libsvn_client/diff.c2723
-rw-r--r--subversion/libsvn_client/diff_local.c633
-rw-r--r--subversion/libsvn_client/diff_summarize.c317
-rw-r--r--subversion/libsvn_client/export.c1589
-rw-r--r--subversion/libsvn_client/externals.c1139
-rw-r--r--subversion/libsvn_client/import.c964
-rw-r--r--subversion/libsvn_client/info.c402
-rw-r--r--subversion/libsvn_client/iprops.c270
-rw-r--r--subversion/libsvn_client/list.c579
-rw-r--r--subversion/libsvn_client/locking_commands.c552
-rw-r--r--subversion/libsvn_client/log.c868
-rw-r--r--subversion/libsvn_client/merge.c12674
-rw-r--r--subversion/libsvn_client/mergeinfo.c2191
-rw-r--r--subversion/libsvn_client/mergeinfo.h414
-rw-r--r--subversion/libsvn_client/patch.c3043
-rw-r--r--subversion/libsvn_client/prop_commands.c1559
-rw-r--r--subversion/libsvn_client/ra.c1147
-rw-r--r--subversion/libsvn_client/relocate.c289
-rw-r--r--subversion/libsvn_client/repos_diff.c1405
-rw-r--r--subversion/libsvn_client/resolved.c148
-rw-r--r--subversion/libsvn_client/revert.c201
-rw-r--r--subversion/libsvn_client/revisions.c191
-rw-r--r--subversion/libsvn_client/status.c767
-rw-r--r--subversion/libsvn_client/switch.c487
-rw-r--r--subversion/libsvn_client/update.c707
-rw-r--r--subversion/libsvn_client/upgrade.c327
-rw-r--r--subversion/libsvn_client/url.c63
-rw-r--r--subversion/libsvn_client/util.c457
-rw-r--r--subversion/libsvn_client/version.c33
-rw-r--r--subversion/libsvn_delta/cancel.c378
-rw-r--r--subversion/libsvn_delta/compat.c2010
-rw-r--r--subversion/libsvn_delta/compose_delta.c837
-rw-r--r--subversion/libsvn_delta/debug_editor.c437
-rw-r--r--subversion/libsvn_delta/debug_editor.h49
-rw-r--r--subversion/libsvn_delta/default_editor.c161
-rw-r--r--subversion/libsvn_delta/delta.h96
-rw-r--r--subversion/libsvn_delta/deprecated.c48
-rw-r--r--subversion/libsvn_delta/depth_filter_editor.c485
-rw-r--r--subversion/libsvn_delta/editor.c956
-rw-r--r--subversion/libsvn_delta/path_driver.c298
-rw-r--r--subversion/libsvn_delta/svndiff.c1103
-rw-r--r--subversion/libsvn_delta/text_delta.c1041
-rw-r--r--subversion/libsvn_delta/version.c33
-rw-r--r--subversion/libsvn_delta/xdelta.c514
-rw-r--r--subversion/libsvn_diff/deprecated.c289
-rw-r--r--subversion/libsvn_diff/diff.c199
-rw-r--r--subversion/libsvn_diff/diff.h217
-rw-r--r--subversion/libsvn_diff/diff3.c529
-rw-r--r--subversion/libsvn_diff/diff4.c314
-rw-r--r--subversion/libsvn_diff/diff_file.c2414
-rw-r--r--subversion/libsvn_diff/diff_memory.c1161
-rw-r--r--subversion/libsvn_diff/diff_tree.c1705
-rw-r--r--subversion/libsvn_diff/lcs.c375
-rw-r--r--subversion/libsvn_diff/parse-diff.c1373
-rw-r--r--subversion/libsvn_diff/token.c198
-rw-r--r--subversion/libsvn_diff/util.c591
-rw-r--r--subversion/libsvn_fs/access.c105
-rw-r--r--subversion/libsvn_fs/editor.c850
-rw-r--r--subversion/libsvn_fs/fs-loader.c1602
-rw-r--r--subversion/libsvn_fs/fs-loader.h502
-rw-r--r--subversion/libsvn_fs_base/bdb/bdb-err.c106
-rw-r--r--subversion/libsvn_fs_base/bdb/bdb-err.h115
-rw-r--r--subversion/libsvn_fs_base/bdb/bdb_compat.c34
-rw-r--r--subversion/libsvn_fs_base/bdb/bdb_compat.h135
-rw-r--r--subversion/libsvn_fs_base/bdb/changes-table.c457
-rw-r--r--subversion/libsvn_fs_base/bdb/changes-table.h94
-rw-r--r--subversion/libsvn_fs_base/bdb/checksum-reps-table.c208
-rw-r--r--subversion/libsvn_fs_base/bdb/checksum-reps-table.h89
-rw-r--r--subversion/libsvn_fs_base/bdb/copies-table.c210
-rw-r--r--subversion/libsvn_fs_base/bdb/copies-table.h93
-rw-r--r--subversion/libsvn_fs_base/bdb/dbt.c170
-rw-r--r--subversion/libsvn_fs_base/bdb/dbt.h120
-rw-r--r--subversion/libsvn_fs_base/bdb/env.c719
-rw-r--r--subversion/libsvn_fs_base/bdb/env.h159
-rw-r--r--subversion/libsvn_fs_base/bdb/lock-tokens-table.c157
-rw-r--r--subversion/libsvn_fs_base/bdb/lock-tokens-table.h96
-rw-r--r--subversion/libsvn_fs_base/bdb/locks-table.c328
-rw-r--r--subversion/libsvn_fs_base/bdb/locks-table.h110
-rw-r--r--subversion/libsvn_fs_base/bdb/miscellaneous-table.c135
-rw-r--r--subversion/libsvn_fs_base/bdb/miscellaneous-table.h71
-rw-r--r--subversion/libsvn_fs_base/bdb/node-origins-table.c145
-rw-r--r--subversion/libsvn_fs_base/bdb/node-origins-table.h76
-rw-r--r--subversion/libsvn_fs_base/bdb/nodes-table.c259
-rw-r--r--subversion/libsvn_fs_base/bdb/nodes-table.h121
-rw-r--r--subversion/libsvn_fs_base/bdb/reps-table.c204
-rw-r--r--subversion/libsvn_fs_base/bdb/reps-table.h94
-rw-r--r--subversion/libsvn_fs_base/bdb/rev-table.c221
-rw-r--r--subversion/libsvn_fs_base/bdb/rev-table.h85
-rw-r--r--subversion/libsvn_fs_base/bdb/strings-table.c541
-rw-r--r--subversion/libsvn_fs_base/bdb/strings-table.h143
-rw-r--r--subversion/libsvn_fs_base/bdb/txn-table.c325
-rw-r--r--subversion/libsvn_fs_base/bdb/txn-table.h100
-rw-r--r--subversion/libsvn_fs_base/bdb/uuids-table.c149
-rw-r--r--subversion/libsvn_fs_base/bdb/uuids-table.h69
-rw-r--r--subversion/libsvn_fs_base/dag.c1758
-rw-r--r--subversion/libsvn_fs_base/dag.h587
-rw-r--r--subversion/libsvn_fs_base/err.c177
-rw-r--r--subversion/libsvn_fs_base/err.h98
-rw-r--r--subversion/libsvn_fs_base/fs.c1436
-rw-r--r--subversion/libsvn_fs_base/fs.h357
-rw-r--r--subversion/libsvn_fs_base/id.c208
-rw-r--r--subversion/libsvn_fs_base/id.h81
-rw-r--r--subversion/libsvn_fs_base/key-gen.c131
-rw-r--r--subversion/libsvn_fs_base/key-gen.h100
-rw-r--r--subversion/libsvn_fs_base/lock.c594
-rw-r--r--subversion/libsvn_fs_base/lock.h120
-rw-r--r--subversion/libsvn_fs_base/node-rev.c126
-rw-r--r--subversion/libsvn_fs_base/node-rev.h101
-rw-r--r--subversion/libsvn_fs_base/notes/TODO137
-rw-r--r--subversion/libsvn_fs_base/notes/fs-history270
-rw-r--r--subversion/libsvn_fs_base/notes/structure1086
-rw-r--r--subversion/libsvn_fs_base/reps-strings.c1617
-rw-r--r--subversion/libsvn_fs_base/reps-strings.h176
-rw-r--r--subversion/libsvn_fs_base/revs-txns.c1067
-rw-r--r--subversion/libsvn_fs_base/revs-txns.h231
-rw-r--r--subversion/libsvn_fs_base/trail.c292
-rw-r--r--subversion/libsvn_fs_base/trail.h239
-rw-r--r--subversion/libsvn_fs_base/tree.c5451
-rw-r--r--subversion/libsvn_fs_base/tree.h99
-rw-r--r--subversion/libsvn_fs_base/util/fs_skels.c1515
-rw-r--r--subversion/libsvn_fs_base/util/fs_skels.h177
-rw-r--r--subversion/libsvn_fs_base/uuid.c116
-rw-r--r--subversion/libsvn_fs_base/uuid.h49
-rw-r--r--subversion/libsvn_fs_fs/caching.c692
-rw-r--r--subversion/libsvn_fs_fs/dag.c1338
-rw-r--r--subversion/libsvn_fs_fs/dag.h581
-rw-r--r--subversion/libsvn_fs_fs/fs.c456
-rw-r--r--subversion/libsvn_fs_fs/fs.h523
-rw-r--r--subversion/libsvn_fs_fs/fs_fs.c11469
-rw-r--r--subversion/libsvn_fs_fs/fs_fs.h575
-rw-r--r--subversion/libsvn_fs_fs/id.c405
-rw-r--r--subversion/libsvn_fs_fs/id.h116
-rw-r--r--subversion/libsvn_fs_fs/key-gen.c159
-rw-r--r--subversion/libsvn_fs_fs/key-gen.h91
-rw-r--r--subversion/libsvn_fs_fs/lock.c1079
-rw-r--r--subversion/libsvn_fs_fs/lock.h103
-rw-r--r--subversion/libsvn_fs_fs/rep-cache-db.h83
-rw-r--r--subversion/libsvn_fs_fs/rep-cache-db.sql65
-rw-r--r--subversion/libsvn_fs_fs/rep-cache.c381
-rw-r--r--subversion/libsvn_fs_fs/rep-cache.h101
-rw-r--r--subversion/libsvn_fs_fs/structure621
-rw-r--r--subversion/libsvn_fs_fs/temp_serializer.c1341
-rw-r--r--subversion/libsvn_fs_fs/temp_serializer.h266
-rw-r--r--subversion/libsvn_fs_fs/tree.c4420
-rw-r--r--subversion/libsvn_fs_fs/tree.h98
-rw-r--r--subversion/libsvn_fs_util/fs-util.c223
-rw-r--r--subversion/libsvn_ra/compat.c952
-rw-r--r--subversion/libsvn_ra/debug_reporter.c151
-rw-r--r--subversion/libsvn_ra/debug_reporter.h49
-rw-r--r--subversion/libsvn_ra/deprecated.c509
-rw-r--r--subversion/libsvn_ra/deprecated.h60
-rw-r--r--subversion/libsvn_ra/editor.c339
-rw-r--r--subversion/libsvn_ra/ra_loader.c1576
-rw-r--r--subversion/libsvn_ra/ra_loader.h562
-rw-r--r--subversion/libsvn_ra/util.c242
-rw-r--r--subversion/libsvn_ra/wrapper_template.h512
-rw-r--r--subversion/libsvn_ra_local/ra_local.h97
-rw-r--r--subversion/libsvn_ra_local/ra_plugin.c1766
-rw-r--r--subversion/libsvn_ra_local/split_url.c97
-rw-r--r--subversion/libsvn_ra_serf/README84
-rw-r--r--subversion/libsvn_ra_serf/blame.c375
-rw-r--r--subversion/libsvn_ra_serf/blncache.c179
-rw-r--r--subversion/libsvn_ra_serf/blncache.h90
-rw-r--r--subversion/libsvn_ra_serf/commit.c2468
-rw-r--r--subversion/libsvn_ra_serf/get_deleted_rev.c178
-rw-r--r--subversion/libsvn_ra_serf/getdate.c161
-rw-r--r--subversion/libsvn_ra_serf/getlocations.c201
-rw-r--r--subversion/libsvn_ra_serf/getlocationsegments.c206
-rw-r--r--subversion/libsvn_ra_serf/getlocks.c277
-rw-r--r--subversion/libsvn_ra_serf/inherited_props.c344
-rw-r--r--subversion/libsvn_ra_serf/locks.c654
-rw-r--r--subversion/libsvn_ra_serf/log.c604
-rw-r--r--subversion/libsvn_ra_serf/merge.c430
-rw-r--r--subversion/libsvn_ra_serf/mergeinfo.c246
-rw-r--r--subversion/libsvn_ra_serf/options.c625
-rw-r--r--subversion/libsvn_ra_serf/property.c1263
-rw-r--r--subversion/libsvn_ra_serf/ra_serf.h1785
-rw-r--r--subversion/libsvn_ra_serf/replay.c920
-rw-r--r--subversion/libsvn_ra_serf/sb_bucket.c185
-rw-r--r--subversion/libsvn_ra_serf/serf.c1246
-rw-r--r--subversion/libsvn_ra_serf/update.c3639
-rw-r--r--subversion/libsvn_ra_serf/util.c2614
-rw-r--r--subversion/libsvn_ra_serf/util_error.c100
-rw-r--r--subversion/libsvn_ra_serf/xml.c825
-rw-r--r--subversion/libsvn_ra_svn/client.c2739
-rw-r--r--subversion/libsvn_ra_svn/cram.c221
-rw-r--r--subversion/libsvn_ra_svn/cyrus_auth.c954
-rw-r--r--subversion/libsvn_ra_svn/deprecated.c234
-rw-r--r--subversion/libsvn_ra_svn/editorp.c1044
-rw-r--r--subversion/libsvn_ra_svn/internal_auth.c121
-rw-r--r--subversion/libsvn_ra_svn/marshal.c2289
-rw-r--r--subversion/libsvn_ra_svn/protocol625
-rw-r--r--subversion/libsvn_ra_svn/ra_svn.h249
-rw-r--r--subversion/libsvn_ra_svn/streams.c255
-rw-r--r--subversion/libsvn_ra_svn/version.c33
-rw-r--r--subversion/libsvn_repos/authz.c1075
-rw-r--r--subversion/libsvn_repos/commit.c1381
-rw-r--r--subversion/libsvn_repos/delta.c1074
-rw-r--r--subversion/libsvn_repos/deprecated.c1017
-rw-r--r--subversion/libsvn_repos/dump.c1503
-rw-r--r--subversion/libsvn_repos/fs-wrap.c844
-rw-r--r--subversion/libsvn_repos/hooks.c890
-rw-r--r--subversion/libsvn_repos/load-fs-vtable.c1140
-rw-r--r--subversion/libsvn_repos/load.c684
-rw-r--r--subversion/libsvn_repos/log.c2369
-rw-r--r--subversion/libsvn_repos/node_tree.c431
-rw-r--r--subversion/libsvn_repos/notify.c44
-rw-r--r--subversion/libsvn_repos/replay.c1591
-rw-r--r--subversion/libsvn_repos/reporter.c1610
-rw-r--r--subversion/libsvn_repos/repos.c2132
-rw-r--r--subversion/libsvn_repos/repos.h425
-rw-r--r--subversion/libsvn_repos/rev_hunt.c1699
-rw-r--r--subversion/libsvn_subr/adler32.c101
-rw-r--r--subversion/libsvn_subr/atomic.c85
-rw-r--r--subversion/libsvn_subr/auth.c652
-rw-r--r--subversion/libsvn_subr/auth.h49
-rw-r--r--subversion/libsvn_subr/base64.c567
-rw-r--r--subversion/libsvn_subr/cache-inprocess.c648
-rw-r--r--subversion/libsvn_subr/cache-membuffer.c2369
-rw-r--r--subversion/libsvn_subr/cache-memcache.c583
-rw-r--r--subversion/libsvn_subr/cache.c265
-rw-r--r--subversion/libsvn_subr/cache.h109
-rw-r--r--subversion/libsvn_subr/cache_config.c169
-rw-r--r--subversion/libsvn_subr/checksum.c500
-rw-r--r--subversion/libsvn_subr/cmdline.c1312
-rw-r--r--subversion/libsvn_subr/compat.c159
-rw-r--r--subversion/libsvn_subr/config.c1208
-rw-r--r--subversion/libsvn_subr/config_auth.c277
-rw-r--r--subversion/libsvn_subr/config_file.c1260
-rw-r--r--subversion/libsvn_subr/config_impl.h161
-rw-r--r--subversion/libsvn_subr/config_win.c259
-rw-r--r--subversion/libsvn_subr/crypto.c705
-rw-r--r--subversion/libsvn_subr/crypto.h141
-rw-r--r--subversion/libsvn_subr/ctype.c319
-rw-r--r--subversion/libsvn_subr/date.c393
-rw-r--r--subversion/libsvn_subr/debug.c155
-rw-r--r--subversion/libsvn_subr/deprecated.c1304
-rw-r--r--subversion/libsvn_subr/dirent_uri.c2597
-rw-r--r--subversion/libsvn_subr/dirent_uri.h40
-rw-r--r--subversion/libsvn_subr/dso.c117
-rw-r--r--subversion/libsvn_subr/eol.c108
-rw-r--r--subversion/libsvn_subr/error.c800
-rwxr-xr-xsubversion/libsvn_subr/genctype.py114
-rw-r--r--subversion/libsvn_subr/gpg_agent.c463
-rw-r--r--subversion/libsvn_subr/hash.c642
-rw-r--r--subversion/libsvn_subr/internal_statements.h76
-rw-r--r--subversion/libsvn_subr/internal_statements.sql47
-rw-r--r--subversion/libsvn_subr/io.c4768
-rw-r--r--subversion/libsvn_subr/iter.c216
-rw-r--r--subversion/libsvn_subr/lock.c60
-rw-r--r--subversion/libsvn_subr/log.c396
-rw-r--r--subversion/libsvn_subr/macos_keychain.c263
-rw-r--r--subversion/libsvn_subr/magic.c161
-rw-r--r--subversion/libsvn_subr/md5.c110
-rw-r--r--subversion/libsvn_subr/md5.h71
-rw-r--r--subversion/libsvn_subr/mergeinfo.c2631
-rw-r--r--subversion/libsvn_subr/mutex.c83
-rw-r--r--subversion/libsvn_subr/named_atomic.c655
-rw-r--r--subversion/libsvn_subr/nls.c132
-rw-r--r--subversion/libsvn_subr/opt.c1240
-rw-r--r--subversion/libsvn_subr/opt.h54
-rw-r--r--subversion/libsvn_subr/path.c1315
-rw-r--r--subversion/libsvn_subr/pool.c142
-rw-r--r--subversion/libsvn_subr/prompt.c954
-rw-r--r--subversion/libsvn_subr/properties.c507
-rw-r--r--subversion/libsvn_subr/pseudo_md5.c422
-rw-r--r--subversion/libsvn_subr/quoprint.c309
-rw-r--r--subversion/libsvn_subr/sha1.c82
-rw-r--r--subversion/libsvn_subr/sha1.h70
-rw-r--r--subversion/libsvn_subr/simple_providers.c734
-rw-r--r--subversion/libsvn_subr/skel.c881
-rw-r--r--subversion/libsvn_subr/sorts.c309
-rw-r--r--subversion/libsvn_subr/spillbuf.c615
-rw-r--r--subversion/libsvn_subr/sqlite.c1294
-rw-r--r--subversion/libsvn_subr/sqlite3wrapper.c62
-rw-r--r--subversion/libsvn_subr/ssl_client_cert_providers.c209
-rw-r--r--subversion/libsvn_subr/ssl_client_cert_pw_providers.c506
-rw-r--r--subversion/libsvn_subr/ssl_server_trust_providers.c234
-rw-r--r--subversion/libsvn_subr/stream.c1826
-rw-r--r--subversion/libsvn_subr/string.c1273
-rw-r--r--subversion/libsvn_subr/subst.c2025
-rw-r--r--subversion/libsvn_subr/sysinfo.c1132
-rw-r--r--subversion/libsvn_subr/sysinfo.h69
-rw-r--r--subversion/libsvn_subr/target.c335
-rw-r--r--subversion/libsvn_subr/temp_serializer.c382
-rw-r--r--subversion/libsvn_subr/time.c277
-rw-r--r--subversion/libsvn_subr/token.c98
-rw-r--r--subversion/libsvn_subr/types.c340
-rw-r--r--subversion/libsvn_subr/user.c86
-rw-r--r--subversion/libsvn_subr/username_providers.c306
-rw-r--r--subversion/libsvn_subr/utf.c1075
-rw-r--r--subversion/libsvn_subr/utf_validate.c485
-rw-r--r--subversion/libsvn_subr/utf_width.c283
-rw-r--r--subversion/libsvn_subr/validate.c102
-rw-r--r--subversion/libsvn_subr/version.c291
-rw-r--r--subversion/libsvn_subr/win32_crashrpt.c805
-rw-r--r--subversion/libsvn_subr/win32_crashrpt.h35
-rw-r--r--subversion/libsvn_subr/win32_crashrpt_dll.h93
-rw-r--r--subversion/libsvn_subr/win32_crypto.c492
-rw-r--r--subversion/libsvn_subr/win32_xlate.c238
-rw-r--r--subversion/libsvn_subr/win32_xlate.h52
-rw-r--r--subversion/libsvn_subr/xml.c655
-rw-r--r--subversion/libsvn_wc/README195
-rw-r--r--subversion/libsvn_wc/adm_crawler.c1239
-rw-r--r--subversion/libsvn_wc/adm_files.c584
-rw-r--r--subversion/libsvn_wc/adm_files.h161
-rw-r--r--subversion/libsvn_wc/adm_ops.c1400
-rw-r--r--subversion/libsvn_wc/ambient_depth_filter_editor.c715
-rw-r--r--subversion/libsvn_wc/cleanup.c231
-rw-r--r--subversion/libsvn_wc/conflicts.c3141
-rw-r--r--subversion/libsvn_wc/conflicts.h443
-rw-r--r--subversion/libsvn_wc/context.c116
-rw-r--r--subversion/libsvn_wc/copy.c1048
-rw-r--r--subversion/libsvn_wc/crop.c361
-rw-r--r--subversion/libsvn_wc/delete.c508
-rw-r--r--subversion/libsvn_wc/deprecated.c4582
-rw-r--r--subversion/libsvn_wc/diff.h144
-rw-r--r--subversion/libsvn_wc/diff_editor.c2747
-rw-r--r--subversion/libsvn_wc/diff_local.c541
-rw-r--r--subversion/libsvn_wc/entries.c2738
-rw-r--r--subversion/libsvn_wc/entries.h164
-rw-r--r--subversion/libsvn_wc/externals.c1686
-rw-r--r--subversion/libsvn_wc/info.c580
-rw-r--r--subversion/libsvn_wc/lock.c1656
-rw-r--r--subversion/libsvn_wc/lock.h91
-rw-r--r--subversion/libsvn_wc/merge.c1424
-rw-r--r--subversion/libsvn_wc/node.c1418
-rw-r--r--subversion/libsvn_wc/old-and-busted.c1340
-rw-r--r--subversion/libsvn_wc/props.c2344
-rw-r--r--subversion/libsvn_wc/props.h154
-rw-r--r--subversion/libsvn_wc/questions.c621
-rw-r--r--subversion/libsvn_wc/relocate.c170
-rw-r--r--subversion/libsvn_wc/revert.c886
-rw-r--r--subversion/libsvn_wc/revision_status.c67
-rw-r--r--subversion/libsvn_wc/status.c3047
-rw-r--r--subversion/libsvn_wc/token-map.h70
-rw-r--r--subversion/libsvn_wc/translate.c452
-rw-r--r--subversion/libsvn_wc/translate.h189
-rw-r--r--subversion/libsvn_wc/tree_conflicts.c513
-rw-r--r--subversion/libsvn_wc/tree_conflicts.h93
-rw-r--r--subversion/libsvn_wc/update_editor.c5486
-rw-r--r--subversion/libsvn_wc/upgrade.c2376
-rw-r--r--subversion/libsvn_wc/util.c636
-rw-r--r--subversion/libsvn_wc/wc-checks.h55
-rw-r--r--subversion/libsvn_wc/wc-checks.sql77
-rw-r--r--subversion/libsvn_wc/wc-metadata.h516
-rw-r--r--subversion/libsvn_wc/wc-metadata.sql951
-rw-r--r--subversion/libsvn_wc/wc-queries.h3100
-rw-r--r--subversion/libsvn_wc/wc-queries.sql1693
-rw-r--r--subversion/libsvn_wc/wc.h808
-rw-r--r--subversion/libsvn_wc/wc_db.c15050
-rw-r--r--subversion/libsvn_wc/wc_db.h3413
-rw-r--r--subversion/libsvn_wc/wc_db_pristine.c925
-rw-r--r--subversion/libsvn_wc/wc_db_private.h458
-rw-r--r--subversion/libsvn_wc/wc_db_update_move.c2631
-rw-r--r--subversion/libsvn_wc/wc_db_util.c228
-rw-r--r--subversion/libsvn_wc/wc_db_wcroot.c900
-rw-r--r--subversion/libsvn_wc/wcroot_anchor.c227
-rw-r--r--subversion/libsvn_wc/workqueue.c1666
-rw-r--r--subversion/libsvn_wc/workqueue.h235
-rw-r--r--subversion/svn/add-cmd.c113
-rw-r--r--subversion/svn/blame-cmd.c419
-rw-r--r--subversion/svn/cat-cmd.c118
-rw-r--r--subversion/svn/changelist-cmd.c149
-rw-r--r--subversion/svn/checkout-cmd.c173
-rw-r--r--subversion/svn/cl-conflicts.c454
-rw-r--r--subversion/svn/cl-conflicts.h80
-rw-r--r--subversion/svn/cl.h852
-rw-r--r--subversion/svn/cleanup-cmd.c104
-rw-r--r--subversion/svn/client_errors.h97
-rw-r--r--subversion/svn/commit-cmd.c186
-rw-r--r--subversion/svn/conflict-callbacks.c1369
-rw-r--r--subversion/svn/copy-cmd.c184
-rw-r--r--subversion/svn/delete-cmd.c95
-rw-r--r--subversion/svn/deprecated.c41
-rw-r--r--subversion/svn/diff-cmd.c476
-rw-r--r--subversion/svn/export-cmd.c128
-rw-r--r--subversion/svn/file-merge.c959
-rw-r--r--subversion/svn/help-cmd.c153
-rw-r--r--subversion/svn/import-cmd.c132
-rw-r--r--subversion/svn/info-cmd.c683
-rw-r--r--subversion/svn/list-cmd.c424
-rw-r--r--subversion/svn/lock-cmd.c110
-rw-r--r--subversion/svn/log-cmd.c875
-rw-r--r--subversion/svn/merge-cmd.c467
-rw-r--r--subversion/svn/mergeinfo-cmd.c349
-rw-r--r--subversion/svn/mkdir-cmd.c104
-rw-r--r--subversion/svn/move-cmd.c105
-rw-r--r--subversion/svn/notify.c1222
-rw-r--r--subversion/svn/patch-cmd.c98
-rw-r--r--subversion/svn/propdel-cmd.c103
-rw-r--r--subversion/svn/propedit-cmd.c356
-rw-r--r--subversion/svn/propget-cmd.c493
-rw-r--r--subversion/svn/proplist-cmd.c336
-rw-r--r--subversion/svn/props.c356
-rw-r--r--subversion/svn/propset-cmd.c191
-rw-r--r--subversion/svn/relocate-cmd.c120
-rw-r--r--subversion/svn/resolve-cmd.c131
-rw-r--r--subversion/svn/resolved-cmd.c88
-rw-r--r--subversion/svn/revert-cmd.c81
-rw-r--r--subversion/svn/schema/blame.rnc42
-rw-r--r--subversion/svn/schema/common.rnc77
-rw-r--r--subversion/svn/schema/diff.rnc39
-rw-r--r--subversion/svn/schema/info.rnc134
-rw-r--r--subversion/svn/schema/list.rnc45
-rw-r--r--subversion/svn/schema/log.rnc55
-rw-r--r--subversion/svn/schema/props.rnc36
-rw-r--r--subversion/svn/schema/status.rnc92
-rw-r--r--subversion/svn/status-cmd.c416
-rw-r--r--subversion/svn/status.c607
-rw-r--r--subversion/svn/svn.147
-rw-r--r--subversion/svn/svn.c2961
-rw-r--r--subversion/svn/switch-cmd.c199
-rw-r--r--subversion/svn/unlock-cmd.c68
-rw-r--r--subversion/svn/update-cmd.c196
-rw-r--r--subversion/svn/upgrade-cmd.c78
-rw-r--r--subversion/svn/util.c1109
-rw-r--r--subversion/svn_private_config.h.in257
-rw-r--r--subversion/svn_private_config.hw110
-rw-r--r--subversion/svnadmin/svnadmin.147
-rw-r--r--subversion/svnadmin/svnadmin.c2380
-rw-r--r--subversion/svndumpfilter/svndumpfilter.147
-rw-r--r--subversion/svndumpfilter/svndumpfilter.c1658
-rw-r--r--subversion/svnlook/svnlook.147
-rw-r--r--subversion/svnlook/svnlook.c2830
-rw-r--r--subversion/svnmucc/svnmucc.147
-rw-r--r--subversion/svnmucc/svnmucc.c1460
-rw-r--r--subversion/svnrdump/dump_editor.c1280
-rw-r--r--subversion/svnrdump/load_editor.c1211
-rw-r--r--subversion/svnrdump/svnrdump.147
-rw-r--r--subversion/svnrdump/svnrdump.c1185
-rw-r--r--subversion/svnrdump/svnrdump.h129
-rw-r--r--subversion/svnrdump/util.c73
-rw-r--r--subversion/svnserve/cyrus_auth.c377
-rw-r--r--subversion/svnserve/log-escape.c143
-rw-r--r--subversion/svnserve/serve.c3678
-rw-r--r--subversion/svnserve/server.h186
-rw-r--r--subversion/svnserve/svnserve.8138
-rw-r--r--subversion/svnserve/svnserve.c1175
-rw-r--r--subversion/svnserve/svnserve.conf.5100
-rw-r--r--subversion/svnserve/winservice.c490
-rw-r--r--subversion/svnserve/winservice.h64
-rw-r--r--subversion/svnsync/svnsync.147
-rw-r--r--subversion/svnsync/svnsync.c2305
-rw-r--r--subversion/svnsync/sync.c643
-rw-r--r--subversion/svnsync/sync.h85
-rw-r--r--subversion/svnversion/svnversion.147
-rw-r--r--subversion/svnversion/svnversion.c300
551 files changed, 402644 insertions, 0 deletions
diff --git a/subversion/include/mod_authz_svn.h b/subversion/include/mod_authz_svn.h
new file mode 100644
index 0000000..2cf1464
--- /dev/null
+++ b/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/subversion/include/mod_dav_svn.h b/subversion/include/mod_dav_svn.h
new file mode 100644
index 0000000..c498c5e
--- /dev/null
+++ b/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/subversion/include/private/README b/subversion/include/private/README
new file mode 100644
index 0000000..05527e2
--- /dev/null
+++ b/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/subversion/include/private/ra_svn_sasl.h b/subversion/include/private/ra_svn_sasl.h
new file mode 100644
index 0000000..428e20e
--- /dev/null
+++ b/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/subversion/include/private/svn_adler32.h b/subversion/include/private/svn_adler32.h
new file mode 100644
index 0000000..8c9bbd2
--- /dev/null
+++ b/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/subversion/include/private/svn_atomic.h b/subversion/include/private/svn_atomic.h
new file mode 100644
index 0000000..187703b
--- /dev/null
+++ b/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/subversion/include/private/svn_auth_private.h b/subversion/include/private/svn_auth_private.h
new file mode 100644
index 0000000..7a1c716
--- /dev/null
+++ b/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/subversion/include/private/svn_cache.h b/subversion/include/private/svn_cache.h
new file mode 100644
index 0000000..df40f7e
--- /dev/null
+++ b/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/subversion/include/private/svn_client_private.h b/subversion/include/private/svn_client_private.h
new file mode 100644
index 0000000..9eebc18
--- /dev/null
+++ b/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/subversion/include/private/svn_cmdline_private.h b/subversion/include/private/svn_cmdline_private.h
new file mode 100644
index 0000000..ad16b66
--- /dev/null
+++ b/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/subversion/include/private/svn_dav_protocol.h b/subversion/include/private/svn_dav_protocol.h
new file mode 100644
index 0000000..94cf069
--- /dev/null
+++ b/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/subversion/include/private/svn_debug.h b/subversion/include/private/svn_debug.h
new file mode 100644
index 0000000..a596ba1
--- /dev/null
+++ b/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/subversion/include/private/svn_delta_private.h b/subversion/include/private/svn_delta_private.h
new file mode 100644
index 0000000..4de85a9
--- /dev/null
+++ b/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/subversion/include/private/svn_dep_compat.h b/subversion/include/private/svn_dep_compat.h
new file mode 100644
index 0000000..71c0b9a
--- /dev/null
+++ b/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/subversion/include/private/svn_diff_private.h b/subversion/include/private/svn_diff_private.h
new file mode 100644
index 0000000..3b6e591
--- /dev/null
+++ b/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/subversion/include/private/svn_diff_tree.h b/subversion/include/private/svn_diff_tree.h
new file mode 100644
index 0000000..597a59b
--- /dev/null
+++ b/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/subversion/include/private/svn_doxygen.h b/subversion/include/private/svn_doxygen.h
new file mode 100644
index 0000000..426e3f7
--- /dev/null
+++ b/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/subversion/include/private/svn_editor.h b/subversion/include/private/svn_editor.h
new file mode 100644
index 0000000..d714bb1
--- /dev/null
+++ b/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/subversion/include/private/svn_eol_private.h b/subversion/include/private/svn_eol_private.h
new file mode 100644
index 0000000..d2cce5c
--- /dev/null
+++ b/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/subversion/include/private/svn_error_private.h b/subversion/include/private/svn_error_private.h
new file mode 100644
index 0000000..f8bd2bc
--- /dev/null
+++ b/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/subversion/include/private/svn_fs_private.h b/subversion/include/private/svn_fs_private.h
new file mode 100644
index 0000000..20b70cd
--- /dev/null
+++ b/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/subversion/include/private/svn_fs_util.h b/subversion/include/private/svn_fs_util.h
new file mode 100644
index 0000000..eb0f024
--- /dev/null
+++ b/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/subversion/include/private/svn_fspath.h b/subversion/include/private/svn_fspath.h
new file mode 100644
index 0000000..01679b9
--- /dev/null
+++ b/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/subversion/include/private/svn_io_private.h b/subversion/include/private/svn_io_private.h
new file mode 100644
index 0000000..2fb4fa5
--- /dev/null
+++ b/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/subversion/include/private/svn_log.h b/subversion/include/private/svn_log.h
new file mode 100644
index 0000000..bdcf32f
--- /dev/null
+++ b/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/subversion/include/private/svn_magic.h b/subversion/include/private/svn_magic.h
new file mode 100644
index 0000000..b057e56
--- /dev/null
+++ b/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/subversion/include/private/svn_mergeinfo_private.h b/subversion/include/private/svn_mergeinfo_private.h
new file mode 100644
index 0000000..287515a
--- /dev/null
+++ b/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/subversion/include/private/svn_mutex.h b/subversion/include/private/svn_mutex.h
new file mode 100644
index 0000000..85583d3
--- /dev/null
+++ b/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/subversion/include/private/svn_named_atomic.h b/subversion/include/private/svn_named_atomic.h
new file mode 100644
index 0000000..4efa255
--- /dev/null
+++ b/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/subversion/include/private/svn_opt_private.h b/subversion/include/private/svn_opt_private.h
new file mode 100644
index 0000000..6ae67a5
--- /dev/null
+++ b/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/subversion/include/private/svn_pseudo_md5.h b/subversion/include/private/svn_pseudo_md5.h
new file mode 100644
index 0000000..34d5929
--- /dev/null
+++ b/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/subversion/include/private/svn_ra_private.h b/subversion/include/private/svn_ra_private.h
new file mode 100644
index 0000000..4531bcb
--- /dev/null
+++ b/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/subversion/include/private/svn_ra_svn_private.h b/subversion/include/private/svn_ra_svn_private.h
new file mode 100644
index 0000000..b4294d0
--- /dev/null
+++ b/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/subversion/include/private/svn_repos_private.h b/subversion/include/private/svn_repos_private.h
new file mode 100644
index 0000000..8e943ae
--- /dev/null
+++ b/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/subversion/include/private/svn_skel.h b/subversion/include/private/svn_skel.h
new file mode 100644
index 0000000..5b17b21
--- /dev/null
+++ b/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/subversion/include/private/svn_sqlite.h b/subversion/include/private/svn_sqlite.h
new file mode 100644
index 0000000..c1d640b
--- /dev/null
+++ b/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/subversion/include/private/svn_string_private.h b/subversion/include/private/svn_string_private.h
new file mode 100644
index 0000000..8579f4d
--- /dev/null
+++ b/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/subversion/include/private/svn_subr_private.h b/subversion/include/private/svn_subr_private.h
new file mode 100644
index 0000000..a45e664
--- /dev/null
+++ b/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/subversion/include/private/svn_temp_serializer.h b/subversion/include/private/svn_temp_serializer.h
new file mode 100644
index 0000000..7a007c3
--- /dev/null
+++ b/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/subversion/include/private/svn_token.h b/subversion/include/private/svn_token.h
new file mode 100644
index 0000000..c7c1c2c
--- /dev/null
+++ b/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/subversion/include/private/svn_utf_private.h b/subversion/include/private/svn_utf_private.h
new file mode 100644
index 0000000..9f5a4ad
--- /dev/null
+++ b/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/subversion/include/private/svn_wc_private.h b/subversion/include/private/svn_wc_private.h
new file mode 100644
index 0000000..fce42b0
--- /dev/null
+++ b/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/subversion/include/svn_auth.h b/subversion/include/svn_auth.h
new file mode 100644
index 0000000..dadc1cf
--- /dev/null
+++ b/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/subversion/include/svn_base64.h b/subversion/include/svn_base64.h
new file mode 100644
index 0000000..cc1820f
--- /dev/null
+++ b/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/subversion/include/svn_cache_config.h b/subversion/include/svn_cache_config.h
new file mode 100644
index 0000000..b03a079
--- /dev/null
+++ b/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/subversion/include/svn_checksum.h b/subversion/include/svn_checksum.h
new file mode 100644
index 0000000..d3271f5
--- /dev/null
+++ b/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/subversion/include/svn_client.h b/subversion/include/svn_client.h
new file mode 100644
index 0000000..d8eacdf
--- /dev/null
+++ b/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/subversion/include/svn_cmdline.h b/subversion/include/svn_cmdline.h
new file mode 100644
index 0000000..80442e4
--- /dev/null
+++ b/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/subversion/include/svn_compat.h b/subversion/include/svn_compat.h
new file mode 100644
index 0000000..a127125
--- /dev/null
+++ b/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/subversion/include/svn_config.h b/subversion/include/svn_config.h
new file mode 100644
index 0000000..c5d6976
--- /dev/null
+++ b/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/subversion/include/svn_ctype.h b/subversion/include/svn_ctype.h
new file mode 100644
index 0000000..1263552
--- /dev/null
+++ b/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/subversion/include/svn_dav.h b/subversion/include/svn_dav.h
new file mode 100644
index 0000000..e9092d5
--- /dev/null
+++ b/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/subversion/include/svn_delta.h b/subversion/include/svn_delta.h
new file mode 100644
index 0000000..7df7f3f
--- /dev/null
+++ b/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/subversion/include/svn_diff.h b/subversion/include/svn_diff.h
new file mode 100644
index 0000000..23b8970
--- /dev/null
+++ b/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/subversion/include/svn_dirent_uri.h b/subversion/include/svn_dirent_uri.h
new file mode 100644
index 0000000..8fb449d
--- /dev/null
+++ b/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/subversion/include/svn_dso.h b/subversion/include/svn_dso.h
new file mode 100644
index 0000000..f5cfa10
--- /dev/null
+++ b/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/subversion/include/svn_error.h b/subversion/include/svn_error.h
new file mode 100644
index 0000000..3a6e4c5
--- /dev/null
+++ b/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/subversion/include/svn_error_codes.h b/subversion/include/svn_error_codes.h
new file mode 100644
index 0000000..222bc2b
--- /dev/null
+++ b/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/subversion/include/svn_fs.h b/subversion/include/svn_fs.h
new file mode 100644
index 0000000..8cef9a3
--- /dev/null
+++ b/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/subversion/include/svn_hash.h b/subversion/include/svn_hash.h
new file mode 100644
index 0000000..46b4760
--- /dev/null
+++ b/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/subversion/include/svn_io.h b/subversion/include/svn_io.h
new file mode 100644
index 0000000..92874e1
--- /dev/null
+++ b/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/subversion/include/svn_iter.h b/subversion/include/svn_iter.h
new file mode 100644
index 0000000..ab88935
--- /dev/null
+++ b/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/subversion/include/svn_md5.h b/subversion/include/svn_md5.h
new file mode 100644
index 0000000..e6e330d
--- /dev/null
+++ b/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/subversion/include/svn_mergeinfo.h b/subversion/include/svn_mergeinfo.h
new file mode 100644
index 0000000..ada70a2
--- /dev/null
+++ b/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/subversion/include/svn_nls.h b/subversion/include/svn_nls.h
new file mode 100644
index 0000000..6a5bc1b
--- /dev/null
+++ b/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/subversion/include/svn_opt.h b/subversion/include/svn_opt.h
new file mode 100644
index 0000000..25da44f
--- /dev/null
+++ b/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/subversion/include/svn_path.h b/subversion/include/svn_path.h
new file mode 100644
index 0000000..3478003
--- /dev/null
+++ b/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/subversion/include/svn_pools.h b/subversion/include/svn_pools.h
new file mode 100644
index 0000000..d4c3a53
--- /dev/null
+++ b/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/subversion/include/svn_props.h b/subversion/include/svn_props.h
new file mode 100644
index 0000000..1f2bbbf
--- /dev/null
+++ b/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/subversion/include/svn_quoprint.h b/subversion/include/svn_quoprint.h
new file mode 100644
index 0000000..abbbe17
--- /dev/null
+++ b/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/subversion/include/svn_ra.h b/subversion/include/svn_ra.h
new file mode 100644
index 0000000..cf6f612
--- /dev/null
+++ b/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/subversion/include/svn_ra_svn.h b/subversion/include/svn_ra_svn.h
new file mode 100644
index 0000000..9c556b8
--- /dev/null
+++ b/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/subversion/include/svn_repos.h b/subversion/include/svn_repos.h
new file mode 100644
index 0000000..2cec6dd
--- /dev/null
+++ b/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/subversion/include/svn_sorts.h b/subversion/include/svn_sorts.h
new file mode 100644
index 0000000..b9e05e5
--- /dev/null
+++ b/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/subversion/include/svn_string.h b/subversion/include/svn_string.h
new file mode 100644
index 0000000..d8ce02b
--- /dev/null
+++ b/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/subversion/include/svn_subst.h b/subversion/include/svn_subst.h
new file mode 100644
index 0000000..98aaf3e
--- /dev/null
+++ b/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/subversion/include/svn_time.h b/subversion/include/svn_time.h
new file mode 100644
index 0000000..76517ca
--- /dev/null
+++ b/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/subversion/include/svn_types.h b/subversion/include/svn_types.h
new file mode 100644
index 0000000..6b3a087
--- /dev/null
+++ b/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/subversion/include/svn_user.h b/subversion/include/svn_user.h
new file mode 100644
index 0000000..65e2820
--- /dev/null
+++ b/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/subversion/include/svn_utf.h b/subversion/include/svn_utf.h
new file mode 100644
index 0000000..4a2c137
--- /dev/null
+++ b/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/subversion/include/svn_version.h b/subversion/include/svn_version.h
new file mode 100644
index 0000000..f8ce7c3
--- /dev/null
+++ b/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/subversion/include/svn_wc.h b/subversion/include/svn_wc.h
new file mode 100644
index 0000000..2a9741d
--- /dev/null
+++ b/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/subversion/include/svn_xml.h b/subversion/include/svn_xml.h
new file mode 100644
index 0000000..90969be
--- /dev/null
+++ b/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/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c b/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c
new file mode 100644
index 0000000..48dfa35
--- /dev/null
+++ b/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/subversion/libsvn_auth_gnome_keyring/version.c b/subversion/libsvn_auth_gnome_keyring/version.c
new file mode 100644
index 0000000..361fe01
--- /dev/null
+++ b/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/subversion/libsvn_auth_kwallet/kwallet.cpp b/subversion/libsvn_auth_kwallet/kwallet.cpp
new file mode 100644
index 0000000..e1a345e
--- /dev/null
+++ b/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/subversion/libsvn_auth_kwallet/version.c b/subversion/libsvn_auth_kwallet/version.c
new file mode 100644
index 0000000..0a46e41
--- /dev/null
+++ b/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/subversion/libsvn_client/add.c b/subversion/libsvn_client/add.c
new file mode 100644
index 0000000..f121bc8
--- /dev/null
+++ b/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/subversion/libsvn_client/blame.c b/subversion/libsvn_client/blame.c
new file mode 100644
index 0000000..188fdd2
--- /dev/null
+++ b/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/subversion/libsvn_client/cat.c b/subversion/libsvn_client/cat.c
new file mode 100644
index 0000000..7c58f88
--- /dev/null
+++ b/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/subversion/libsvn_client/changelist.c b/subversion/libsvn_client/changelist.c
new file mode 100644
index 0000000..fc4d987
--- /dev/null
+++ b/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/subversion/libsvn_client/checkout.c b/subversion/libsvn_client/checkout.c
new file mode 100644
index 0000000..41be776
--- /dev/null
+++ b/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/subversion/libsvn_client/cleanup.c b/subversion/libsvn_client/cleanup.c
new file mode 100644
index 0000000..b15e824
--- /dev/null
+++ b/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/subversion/libsvn_client/client.h b/subversion/libsvn_client/client.h
new file mode 100644
index 0000000..9ea25f2
--- /dev/null
+++ b/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/subversion/libsvn_client/cmdline.c b/subversion/libsvn_client/cmdline.c
new file mode 100644
index 0000000..a17f8c4
--- /dev/null
+++ b/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/subversion/libsvn_client/commit.c b/subversion/libsvn_client/commit.c
new file mode 100644
index 0000000..3f6bfef
--- /dev/null
+++ b/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/subversion/libsvn_client/commit_util.c b/subversion/libsvn_client/commit_util.c
new file mode 100644
index 0000000..1e2c50c
--- /dev/null
+++ b/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/subversion/libsvn_client/compat_providers.c b/subversion/libsvn_client/compat_providers.c
new file mode 100644
index 0000000..ae53a15
--- /dev/null
+++ b/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/subversion/libsvn_client/copy.c b/subversion/libsvn_client/copy.c
new file mode 100644
index 0000000..c0501b9
--- /dev/null
+++ b/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/subversion/libsvn_client/copy_foreign.c b/subversion/libsvn_client/copy_foreign.c
new file mode 100644
index 0000000..8de8a5d
--- /dev/null
+++ b/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/subversion/libsvn_client/ctx.c b/subversion/libsvn_client/ctx.c
new file mode 100644
index 0000000..185b91e
--- /dev/null
+++ b/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/subversion/libsvn_client/delete.c b/subversion/libsvn_client/delete.c
new file mode 100644
index 0000000..2f4ee66
--- /dev/null
+++ b/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/subversion/libsvn_client/deprecated.c b/subversion/libsvn_client/deprecated.c
new file mode 100644
index 0000000..a67a69b
--- /dev/null
+++ b/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/subversion/libsvn_client/diff.c b/subversion/libsvn_client/diff.c
new file mode 100644
index 0000000..a5a36bd
--- /dev/null
+++ b/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/subversion/libsvn_client/diff_local.c b/subversion/libsvn_client/diff_local.c
new file mode 100644
index 0000000..cc7184f
--- /dev/null
+++ b/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/subversion/libsvn_client/diff_summarize.c b/subversion/libsvn_client/diff_summarize.c
new file mode 100644
index 0000000..df0911b
--- /dev/null
+++ b/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/subversion/libsvn_client/export.c b/subversion/libsvn_client/export.c
new file mode 100644
index 0000000..d6022ed
--- /dev/null
+++ b/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/subversion/libsvn_client/externals.c b/subversion/libsvn_client/externals.c
new file mode 100644
index 0000000..30748ea
--- /dev/null
+++ b/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/subversion/libsvn_client/import.c b/subversion/libsvn_client/import.c
new file mode 100644
index 0000000..43e0d79
--- /dev/null
+++ b/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/subversion/libsvn_client/info.c b/subversion/libsvn_client/info.c
new file mode 100644
index 0000000..f49f22e
--- /dev/null
+++ b/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/subversion/libsvn_client/iprops.c b/subversion/libsvn_client/iprops.c
new file mode 100644
index 0000000..653ce8c
--- /dev/null
+++ b/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/subversion/libsvn_client/list.c b/subversion/libsvn_client/list.c
new file mode 100644
index 0000000..4093893
--- /dev/null
+++ b/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/subversion/libsvn_client/locking_commands.c b/subversion/libsvn_client/locking_commands.c
new file mode 100644
index 0000000..c768503
--- /dev/null
+++ b/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/subversion/libsvn_client/log.c b/subversion/libsvn_client/log.c
new file mode 100644
index 0000000..ca3edac
--- /dev/null
+++ b/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/subversion/libsvn_client/merge.c b/subversion/libsvn_client/merge.c
new file mode 100644
index 0000000..884d63d
--- /dev/null
+++ b/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/subversion/libsvn_client/mergeinfo.c b/subversion/libsvn_client/mergeinfo.c
new file mode 100644
index 0000000..453cc66
--- /dev/null
+++ b/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/subversion/libsvn_client/mergeinfo.h b/subversion/libsvn_client/mergeinfo.h
new file mode 100644
index 0000000..0c4cf05
--- /dev/null
+++ b/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/subversion/libsvn_client/patch.c b/subversion/libsvn_client/patch.c
new file mode 100644
index 0000000..b965646
--- /dev/null
+++ b/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/subversion/libsvn_client/prop_commands.c b/subversion/libsvn_client/prop_commands.c
new file mode 100644
index 0000000..c3c1cfa
--- /dev/null
+++ b/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/subversion/libsvn_client/ra.c b/subversion/libsvn_client/ra.c
new file mode 100644
index 0000000..33d3de5
--- /dev/null
+++ b/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/subversion/libsvn_client/relocate.c b/subversion/libsvn_client/relocate.c
new file mode 100644
index 0000000..ed8d09c
--- /dev/null
+++ b/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/subversion/libsvn_client/repos_diff.c b/subversion/libsvn_client/repos_diff.c
new file mode 100644
index 0000000..6a7725f
--- /dev/null
+++ b/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/subversion/libsvn_client/resolved.c b/subversion/libsvn_client/resolved.c
new file mode 100644
index 0000000..0496371
--- /dev/null
+++ b/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/subversion/libsvn_client/revert.c b/subversion/libsvn_client/revert.c
new file mode 100644
index 0000000..681e39c
--- /dev/null
+++ b/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/subversion/libsvn_client/revisions.c b/subversion/libsvn_client/revisions.c
new file mode 100644
index 0000000..ec255c1
--- /dev/null
+++ b/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/subversion/libsvn_client/status.c b/subversion/libsvn_client/status.c
new file mode 100644
index 0000000..e581d37
--- /dev/null
+++ b/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/subversion/libsvn_client/switch.c b/subversion/libsvn_client/switch.c
new file mode 100644
index 0000000..fae03de
--- /dev/null
+++ b/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/subversion/libsvn_client/update.c b/subversion/libsvn_client/update.c
new file mode 100644
index 0000000..21f33ec
--- /dev/null
+++ b/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/subversion/libsvn_client/upgrade.c b/subversion/libsvn_client/upgrade.c
new file mode 100644
index 0000000..b9f3235
--- /dev/null
+++ b/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/subversion/libsvn_client/url.c b/subversion/libsvn_client/url.c
new file mode 100644
index 0000000..36019ad
--- /dev/null
+++ b/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/subversion/libsvn_client/util.c b/subversion/libsvn_client/util.c
new file mode 100644
index 0000000..5ac0b8f
--- /dev/null
+++ b/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/subversion/libsvn_client/version.c b/subversion/libsvn_client/version.c
new file mode 100644
index 0000000..2ad6417
--- /dev/null
+++ b/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/subversion/libsvn_delta/cancel.c b/subversion/libsvn_delta/cancel.c
new file mode 100644
index 0000000..89995a8
--- /dev/null
+++ b/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/subversion/libsvn_delta/compat.c b/subversion/libsvn_delta/compat.c
new file mode 100644
index 0000000..8d315d1
--- /dev/null
+++ b/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/subversion/libsvn_delta/compose_delta.c b/subversion/libsvn_delta/compose_delta.c
new file mode 100644
index 0000000..7b96438
--- /dev/null
+++ b/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/subversion/libsvn_delta/debug_editor.c b/subversion/libsvn_delta/debug_editor.c
new file mode 100644
index 0000000..7c2cdec
--- /dev/null
+++ b/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/subversion/libsvn_delta/debug_editor.h b/subversion/libsvn_delta/debug_editor.h
new file mode 100644
index 0000000..2b031af
--- /dev/null
+++ b/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/subversion/libsvn_delta/default_editor.c b/subversion/libsvn_delta/default_editor.c
new file mode 100644
index 0000000..2f1c973
--- /dev/null
+++ b/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/subversion/libsvn_delta/delta.h b/subversion/libsvn_delta/delta.h
new file mode 100644
index 0000000..95fe4f7
--- /dev/null
+++ b/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/subversion/libsvn_delta/deprecated.c b/subversion/libsvn_delta/deprecated.c
new file mode 100644
index 0000000..4171244
--- /dev/null
+++ b/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/subversion/libsvn_delta/depth_filter_editor.c b/subversion/libsvn_delta/depth_filter_editor.c
new file mode 100644
index 0000000..b161979
--- /dev/null
+++ b/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/subversion/libsvn_delta/editor.c b/subversion/libsvn_delta/editor.c
new file mode 100644
index 0000000..1dc94b2
--- /dev/null
+++ b/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/subversion/libsvn_delta/path_driver.c b/subversion/libsvn_delta/path_driver.c
new file mode 100644
index 0000000..62e703a
--- /dev/null
+++ b/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/subversion/libsvn_delta/svndiff.c b/subversion/libsvn_delta/svndiff.c
new file mode 100644
index 0000000..f4b9dc6
--- /dev/null
+++ b/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/subversion/libsvn_delta/text_delta.c b/subversion/libsvn_delta/text_delta.c
new file mode 100644
index 0000000..be2c434
--- /dev/null
+++ b/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/subversion/libsvn_delta/version.c b/subversion/libsvn_delta/version.c
new file mode 100644
index 0000000..ffbfb0f
--- /dev/null
+++ b/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/subversion/libsvn_delta/xdelta.c b/subversion/libsvn_delta/xdelta.c
new file mode 100644
index 0000000..2075a51
--- /dev/null
+++ b/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/subversion/libsvn_diff/deprecated.c b/subversion/libsvn_diff/deprecated.c
new file mode 100644
index 0000000..891ad5f
--- /dev/null
+++ b/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/subversion/libsvn_diff/diff.c b/subversion/libsvn_diff/diff.c
new file mode 100644
index 0000000..f43a3be
--- /dev/null
+++ b/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/subversion/libsvn_diff/diff.h b/subversion/libsvn_diff/diff.h
new file mode 100644
index 0000000..51a84c6
--- /dev/null
+++ b/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/subversion/libsvn_diff/diff3.c b/subversion/libsvn_diff/diff3.c
new file mode 100644
index 0000000..8b7c9b3
--- /dev/null
+++ b/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/subversion/libsvn_diff/diff4.c b/subversion/libsvn_diff/diff4.c
new file mode 100644
index 0000000..9f3cb8c
--- /dev/null
+++ b/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/subversion/libsvn_diff/diff_file.c b/subversion/libsvn_diff/diff_file.c
new file mode 100644
index 0000000..e70c2f9
--- /dev/null
+++ b/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/subversion/libsvn_diff/diff_memory.c b/subversion/libsvn_diff/diff_memory.c
new file mode 100644
index 0000000..00f4c7f
--- /dev/null
+++ b/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/subversion/libsvn_diff/diff_tree.c b/subversion/libsvn_diff/diff_tree.c
new file mode 100644
index 0000000..8490179
--- /dev/null
+++ b/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/subversion/libsvn_diff/lcs.c b/subversion/libsvn_diff/lcs.c
new file mode 100644
index 0000000..8087a92
--- /dev/null
+++ b/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/subversion/libsvn_diff/parse-diff.c b/subversion/libsvn_diff/parse-diff.c
new file mode 100644
index 0000000..a01b4d5
--- /dev/null
+++ b/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/subversion/libsvn_diff/token.c b/subversion/libsvn_diff/token.c
new file mode 100644
index 0000000..6388d9f
--- /dev/null
+++ b/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/subversion/libsvn_diff/util.c b/subversion/libsvn_diff/util.c
new file mode 100644
index 0000000..9e1f411
--- /dev/null
+++ b/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/subversion/libsvn_fs/access.c b/subversion/libsvn_fs/access.c
new file mode 100644
index 0000000..9918be4
--- /dev/null
+++ b/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/subversion/libsvn_fs/editor.c b/subversion/libsvn_fs/editor.c
new file mode 100644
index 0000000..a75f210
--- /dev/null
+++ b/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/subversion/libsvn_fs/fs-loader.c b/subversion/libsvn_fs/fs-loader.c
new file mode 100644
index 0000000..01d6ba1
--- /dev/null
+++ b/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/subversion/libsvn_fs/fs-loader.h b/subversion/libsvn_fs/fs-loader.h
new file mode 100644
index 0000000..532ff05
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/bdb-err.c b/subversion/libsvn_fs_base/bdb/bdb-err.c
new file mode 100644
index 0000000..3d51711
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/bdb-err.h b/subversion/libsvn_fs_base/bdb/bdb-err.h
new file mode 100644
index 0000000..200afe9
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/bdb_compat.c b/subversion/libsvn_fs_base/bdb/bdb_compat.c
new file mode 100644
index 0000000..5596eee
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/bdb_compat.h b/subversion/libsvn_fs_base/bdb/bdb_compat.h
new file mode 100644
index 0000000..bea62de
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/changes-table.c b/subversion/libsvn_fs_base/bdb/changes-table.c
new file mode 100644
index 0000000..80ff468
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/changes-table.h b/subversion/libsvn_fs_base/bdb/changes-table.h
new file mode 100644
index 0000000..c9df636
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/checksum-reps-table.c b/subversion/libsvn_fs_base/bdb/checksum-reps-table.c
new file mode 100644
index 0000000..f4a34c3
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/checksum-reps-table.h b/subversion/libsvn_fs_base/bdb/checksum-reps-table.h
new file mode 100644
index 0000000..ccdcd48
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/copies-table.c b/subversion/libsvn_fs_base/bdb/copies-table.c
new file mode 100644
index 0000000..7bf6ca8
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/copies-table.h b/subversion/libsvn_fs_base/bdb/copies-table.h
new file mode 100644
index 0000000..08ab139
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/dbt.c b/subversion/libsvn_fs_base/bdb/dbt.c
new file mode 100644
index 0000000..a18ba47
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/dbt.h b/subversion/libsvn_fs_base/bdb/dbt.h
new file mode 100644
index 0000000..db93d77
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/env.c b/subversion/libsvn_fs_base/bdb/env.c
new file mode 100644
index 0000000..557c9dc
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/env.h b/subversion/libsvn_fs_base/bdb/env.h
new file mode 100644
index 0000000..a8cce4e
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/lock-tokens-table.c b/subversion/libsvn_fs_base/bdb/lock-tokens-table.c
new file mode 100644
index 0000000..e70ef17
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/lock-tokens-table.h b/subversion/libsvn_fs_base/bdb/lock-tokens-table.h
new file mode 100644
index 0000000..de958ce
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/locks-table.c b/subversion/libsvn_fs_base/bdb/locks-table.c
new file mode 100644
index 0000000..a22663f
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/locks-table.h b/subversion/libsvn_fs_base/bdb/locks-table.h
new file mode 100644
index 0000000..e0d087c
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/miscellaneous-table.c b/subversion/libsvn_fs_base/bdb/miscellaneous-table.c
new file mode 100644
index 0000000..21a05ca
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/miscellaneous-table.h b/subversion/libsvn_fs_base/bdb/miscellaneous-table.h
new file mode 100644
index 0000000..f972d02
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/node-origins-table.c b/subversion/libsvn_fs_base/bdb/node-origins-table.c
new file mode 100644
index 0000000..48dc43b
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/node-origins-table.h b/subversion/libsvn_fs_base/bdb/node-origins-table.h
new file mode 100644
index 0000000..44587ca
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/nodes-table.c b/subversion/libsvn_fs_base/bdb/nodes-table.c
new file mode 100644
index 0000000..d0f475f
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/nodes-table.h b/subversion/libsvn_fs_base/bdb/nodes-table.h
new file mode 100644
index 0000000..c1687ab
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/reps-table.c b/subversion/libsvn_fs_base/bdb/reps-table.c
new file mode 100644
index 0000000..1c8ea6d
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/reps-table.h b/subversion/libsvn_fs_base/bdb/reps-table.h
new file mode 100644
index 0000000..b5cd27d
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/rev-table.c b/subversion/libsvn_fs_base/bdb/rev-table.c
new file mode 100644
index 0000000..b752249
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/rev-table.h b/subversion/libsvn_fs_base/bdb/rev-table.h
new file mode 100644
index 0000000..47209a8
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/strings-table.c b/subversion/libsvn_fs_base/bdb/strings-table.c
new file mode 100644
index 0000000..f5348e7
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/strings-table.h b/subversion/libsvn_fs_base/bdb/strings-table.h
new file mode 100644
index 0000000..443cb72
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/txn-table.c b/subversion/libsvn_fs_base/bdb/txn-table.c
new file mode 100644
index 0000000..54a0e28
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/txn-table.h b/subversion/libsvn_fs_base/bdb/txn-table.h
new file mode 100644
index 0000000..ff0cc9c
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/uuids-table.c b/subversion/libsvn_fs_base/bdb/uuids-table.c
new file mode 100644
index 0000000..0481894
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/bdb/uuids-table.h b/subversion/libsvn_fs_base/bdb/uuids-table.h
new file mode 100644
index 0000000..f6d38df
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/dag.c b/subversion/libsvn_fs_base/dag.c
new file mode 100644
index 0000000..510ccbb
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/dag.h b/subversion/libsvn_fs_base/dag.h
new file mode 100644
index 0000000..4c50c84
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/err.c b/subversion/libsvn_fs_base/err.c
new file mode 100644
index 0000000..c1e691d
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/err.h b/subversion/libsvn_fs_base/err.h
new file mode 100644
index 0000000..5c03d9f
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/fs.c b/subversion/libsvn_fs_base/fs.c
new file mode 100644
index 0000000..bee921b
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/fs.h b/subversion/libsvn_fs_base/fs.h
new file mode 100644
index 0000000..017c898
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/id.c b/subversion/libsvn_fs_base/id.c
new file mode 100644
index 0000000..c063d02
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/id.h b/subversion/libsvn_fs_base/id.h
new file mode 100644
index 0000000..4cdb45c
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/key-gen.c b/subversion/libsvn_fs_base/key-gen.c
new file mode 100644
index 0000000..411207d
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/key-gen.h b/subversion/libsvn_fs_base/key-gen.h
new file mode 100644
index 0000000..e1cd1aa
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/lock.c b/subversion/libsvn_fs_base/lock.c
new file mode 100644
index 0000000..79f72cc
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/lock.h b/subversion/libsvn_fs_base/lock.h
new file mode 100644
index 0000000..603e78c
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/node-rev.c b/subversion/libsvn_fs_base/node-rev.c
new file mode 100644
index 0000000..949a24b
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/node-rev.h b/subversion/libsvn_fs_base/node-rev.h
new file mode 100644
index 0000000..206be20
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/notes/TODO b/subversion/libsvn_fs_base/notes/TODO
new file mode 100644
index 0000000..c72af03
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/notes/fs-history b/subversion/libsvn_fs_base/notes/fs-history
new file mode 100644
index 0000000..e4f51a6
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/notes/structure b/subversion/libsvn_fs_base/notes/structure
new file mode 100644
index 0000000..8e2159f
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/reps-strings.c b/subversion/libsvn_fs_base/reps-strings.c
new file mode 100644
index 0000000..553075d
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/reps-strings.h b/subversion/libsvn_fs_base/reps-strings.h
new file mode 100644
index 0000000..475af0c
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/revs-txns.c b/subversion/libsvn_fs_base/revs-txns.c
new file mode 100644
index 0000000..d218843
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/revs-txns.h b/subversion/libsvn_fs_base/revs-txns.h
new file mode 100644
index 0000000..558a90c
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/trail.c b/subversion/libsvn_fs_base/trail.c
new file mode 100644
index 0000000..8fdf9be
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/trail.h b/subversion/libsvn_fs_base/trail.h
new file mode 100644
index 0000000..87fc313
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/tree.c b/subversion/libsvn_fs_base/tree.c
new file mode 100644
index 0000000..e603af4
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/tree.h b/subversion/libsvn_fs_base/tree.h
new file mode 100644
index 0000000..2e81a17
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/util/fs_skels.c b/subversion/libsvn_fs_base/util/fs_skels.c
new file mode 100644
index 0000000..f3466b9
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/util/fs_skels.h b/subversion/libsvn_fs_base/util/fs_skels.h
new file mode 100644
index 0000000..63dab80
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/uuid.c b/subversion/libsvn_fs_base/uuid.c
new file mode 100644
index 0000000..c865df3
--- /dev/null
+++ b/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/subversion/libsvn_fs_base/uuid.h b/subversion/libsvn_fs_base/uuid.h
new file mode 100644
index 0000000..453a390
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/caching.c b/subversion/libsvn_fs_fs/caching.c
new file mode 100644
index 0000000..4af48b8
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/dag.c b/subversion/libsvn_fs_fs/dag.c
new file mode 100644
index 0000000..3c51ffd
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/dag.h b/subversion/libsvn_fs_fs/dag.h
new file mode 100644
index 0000000..867b025
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/fs.c b/subversion/libsvn_fs_fs/fs.c
new file mode 100644
index 0000000..4f3a340
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/fs.h b/subversion/libsvn_fs_fs/fs.h
new file mode 100644
index 0000000..ea301f6
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/fs_fs.c b/subversion/libsvn_fs_fs/fs_fs.c
new file mode 100644
index 0000000..0354a1f
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/fs_fs.h b/subversion/libsvn_fs_fs/fs_fs.h
new file mode 100644
index 0000000..c09f861
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/id.c b/subversion/libsvn_fs_fs/id.c
new file mode 100644
index 0000000..1317829
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/id.h b/subversion/libsvn_fs_fs/id.h
new file mode 100644
index 0000000..11da466
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/key-gen.c b/subversion/libsvn_fs_fs/key-gen.c
new file mode 100644
index 0000000..a65c59d
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/key-gen.h b/subversion/libsvn_fs_fs/key-gen.h
new file mode 100644
index 0000000..e1b3858
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/lock.c b/subversion/libsvn_fs_fs/lock.c
new file mode 100644
index 0000000..95bd943
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/lock.h b/subversion/libsvn_fs_fs/lock.h
new file mode 100644
index 0000000..1acc79e
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/rep-cache-db.h b/subversion/libsvn_fs_fs/rep-cache-db.h
new file mode 100644
index 0000000..ed379a1
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/rep-cache-db.sql b/subversion/libsvn_fs_fs/rep-cache-db.sql
new file mode 100644
index 0000000..b88c3e0
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/rep-cache.c b/subversion/libsvn_fs_fs/rep-cache.c
new file mode 100644
index 0000000..3a94690
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/rep-cache.h b/subversion/libsvn_fs_fs/rep-cache.h
new file mode 100644
index 0000000..3ccb056
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/structure b/subversion/libsvn_fs_fs/structure
new file mode 100644
index 0000000..41caf1d
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/temp_serializer.c b/subversion/libsvn_fs_fs/temp_serializer.c
new file mode 100644
index 0000000..0178143
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/temp_serializer.h b/subversion/libsvn_fs_fs/temp_serializer.h
new file mode 100644
index 0000000..1009d63
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/tree.c b/subversion/libsvn_fs_fs/tree.c
new file mode 100644
index 0000000..c14955d
--- /dev/null
+++ b/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/subversion/libsvn_fs_fs/tree.h b/subversion/libsvn_fs_fs/tree.h
new file mode 100644
index 0000000..34fa0a23b
--- /dev/null
+++ b/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/subversion/libsvn_fs_util/fs-util.c b/subversion/libsvn_fs_util/fs-util.c
new file mode 100644
index 0000000..da57bc9
--- /dev/null
+++ b/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/subversion/libsvn_ra/compat.c b/subversion/libsvn_ra/compat.c
new file mode 100644
index 0000000..b16bbef
--- /dev/null
+++ b/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/subversion/libsvn_ra/debug_reporter.c b/subversion/libsvn_ra/debug_reporter.c
new file mode 100644
index 0000000..00ec029
--- /dev/null
+++ b/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/subversion/libsvn_ra/debug_reporter.h b/subversion/libsvn_ra/debug_reporter.h
new file mode 100644
index 0000000..1728139
--- /dev/null
+++ b/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/subversion/libsvn_ra/deprecated.c b/subversion/libsvn_ra/deprecated.c
new file mode 100644
index 0000000..b7e717e
--- /dev/null
+++ b/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/subversion/libsvn_ra/deprecated.h b/subversion/libsvn_ra/deprecated.h
new file mode 100644
index 0000000..205de16
--- /dev/null
+++ b/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/subversion/libsvn_ra/editor.c b/subversion/libsvn_ra/editor.c
new file mode 100644
index 0000000..ac969ca
--- /dev/null
+++ b/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/subversion/libsvn_ra/ra_loader.c b/subversion/libsvn_ra/ra_loader.c
new file mode 100644
index 0000000..4afcb59
--- /dev/null
+++ b/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/subversion/libsvn_ra/ra_loader.h b/subversion/libsvn_ra/ra_loader.h
new file mode 100644
index 0000000..4d86121
--- /dev/null
+++ b/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/subversion/libsvn_ra/util.c b/subversion/libsvn_ra/util.c
new file mode 100644
index 0000000..47e4865
--- /dev/null
+++ b/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/subversion/libsvn_ra/wrapper_template.h b/subversion/libsvn_ra/wrapper_template.h
new file mode 100644
index 0000000..cee3fb3
--- /dev/null
+++ b/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/subversion/libsvn_ra_local/ra_local.h b/subversion/libsvn_ra_local/ra_local.h
new file mode 100644
index 0000000..df455da
--- /dev/null
+++ b/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/subversion/libsvn_ra_local/ra_plugin.c b/subversion/libsvn_ra_local/ra_plugin.c
new file mode 100644
index 0000000..e7e8021
--- /dev/null
+++ b/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/subversion/libsvn_ra_local/split_url.c b/subversion/libsvn_ra_local/split_url.c
new file mode 100644
index 0000000..d08bb26
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/README b/subversion/libsvn_ra_serf/README
new file mode 100644
index 0000000..d3baf33
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/blame.c b/subversion/libsvn_ra_serf/blame.c
new file mode 100644
index 0000000..fa4243c
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/blncache.c b/subversion/libsvn_ra_serf/blncache.c
new file mode 100644
index 0000000..d6abcdf
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/blncache.h b/subversion/libsvn_ra_serf/blncache.h
new file mode 100644
index 0000000..5ad4eba
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/commit.c b/subversion/libsvn_ra_serf/commit.c
new file mode 100644
index 0000000..aaf1717
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/get_deleted_rev.c b/subversion/libsvn_ra_serf/get_deleted_rev.c
new file mode 100644
index 0000000..40f6b1d
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/getdate.c b/subversion/libsvn_ra_serf/getdate.c
new file mode 100644
index 0000000..867e86f
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/getlocations.c b/subversion/libsvn_ra_serf/getlocations.c
new file mode 100644
index 0000000..d3e3175
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/getlocationsegments.c b/subversion/libsvn_ra_serf/getlocationsegments.c
new file mode 100644
index 0000000..d2b69bf
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/getlocks.c b/subversion/libsvn_ra_serf/getlocks.c
new file mode 100644
index 0000000..61b8b8c
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/inherited_props.c b/subversion/libsvn_ra_serf/inherited_props.c
new file mode 100644
index 0000000..9283a5a
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/locks.c b/subversion/libsvn_ra_serf/locks.c
new file mode 100644
index 0000000..db2d371
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/log.c b/subversion/libsvn_ra_serf/log.c
new file mode 100644
index 0000000..e44753b
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/merge.c b/subversion/libsvn_ra_serf/merge.c
new file mode 100644
index 0000000..670e421
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/mergeinfo.c b/subversion/libsvn_ra_serf/mergeinfo.c
new file mode 100644
index 0000000..b0bf833
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/options.c b/subversion/libsvn_ra_serf/options.c
new file mode 100644
index 0000000..0af0b15
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/property.c b/subversion/libsvn_ra_serf/property.c
new file mode 100644
index 0000000..63972e8
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/ra_serf.h b/subversion/libsvn_ra_serf/ra_serf.h
new file mode 100644
index 0000000..3f3f3de
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/replay.c b/subversion/libsvn_ra_serf/replay.c
new file mode 100644
index 0000000..1fcecf4
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/sb_bucket.c b/subversion/libsvn_ra_serf/sb_bucket.c
new file mode 100644
index 0000000..df0541f
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c
new file mode 100644
index 0000000..6016157
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/update.c b/subversion/libsvn_ra_serf/update.c
new file mode 100644
index 0000000..21ed2df
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c
new file mode 100644
index 0000000..c7a1716
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/util_error.c b/subversion/libsvn_ra_serf/util_error.c
new file mode 100644
index 0000000..da66091
--- /dev/null
+++ b/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/subversion/libsvn_ra_serf/xml.c b/subversion/libsvn_ra_serf/xml.c
new file mode 100644
index 0000000..95017d5
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/client.c b/subversion/libsvn_ra_svn/client.c
new file mode 100644
index 0000000..4b3762c
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/cram.c b/subversion/libsvn_ra_svn/cram.c
new file mode 100644
index 0000000..1e54ac8
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/cyrus_auth.c b/subversion/libsvn_ra_svn/cyrus_auth.c
new file mode 100644
index 0000000..82e33d3
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/deprecated.c b/subversion/libsvn_ra_svn/deprecated.c
new file mode 100644
index 0000000..8182a4d
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/editorp.c b/subversion/libsvn_ra_svn/editorp.c
new file mode 100644
index 0000000..cc1d8ab
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/internal_auth.c b/subversion/libsvn_ra_svn/internal_auth.c
new file mode 100644
index 0000000..eac2ccd
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/marshal.c b/subversion/libsvn_ra_svn/marshal.c
new file mode 100644
index 0000000..7cf483f
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/protocol b/subversion/libsvn_ra_svn/protocol
new file mode 100644
index 0000000..059873c
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/ra_svn.h b/subversion/libsvn_ra_svn/ra_svn.h
new file mode 100644
index 0000000..dc70eb7
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/streams.c b/subversion/libsvn_ra_svn/streams.c
new file mode 100644
index 0000000..4ae93d5
--- /dev/null
+++ b/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/subversion/libsvn_ra_svn/version.c b/subversion/libsvn_ra_svn/version.c
new file mode 100644
index 0000000..251a4c1
--- /dev/null
+++ b/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/subversion/libsvn_repos/authz.c b/subversion/libsvn_repos/authz.c
new file mode 100644
index 0000000..af4a1f2
--- /dev/null
+++ b/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/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c
new file mode 100644
index 0000000..c4606ab
--- /dev/null
+++ b/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/subversion/libsvn_repos/delta.c b/subversion/libsvn_repos/delta.c
new file mode 100644
index 0000000..51cfda7
--- /dev/null
+++ b/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/subversion/libsvn_repos/deprecated.c b/subversion/libsvn_repos/deprecated.c
new file mode 100644
index 0000000..7208ba6
--- /dev/null
+++ b/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/subversion/libsvn_repos/dump.c b/subversion/libsvn_repos/dump.c
new file mode 100644
index 0000000..75843d7
--- /dev/null
+++ b/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/subversion/libsvn_repos/fs-wrap.c b/subversion/libsvn_repos/fs-wrap.c
new file mode 100644
index 0000000..b759c57
--- /dev/null
+++ b/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/subversion/libsvn_repos/hooks.c b/subversion/libsvn_repos/hooks.c
new file mode 100644
index 0000000..9727599
--- /dev/null
+++ b/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/subversion/libsvn_repos/load-fs-vtable.c b/subversion/libsvn_repos/load-fs-vtable.c
new file mode 100644
index 0000000..c8c5e95
--- /dev/null
+++ b/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/subversion/libsvn_repos/load.c b/subversion/libsvn_repos/load.c
new file mode 100644
index 0000000..691ff92
--- /dev/null
+++ b/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/subversion/libsvn_repos/log.c b/subversion/libsvn_repos/log.c
new file mode 100644
index 0000000..8ca870b
--- /dev/null
+++ b/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/subversion/libsvn_repos/node_tree.c b/subversion/libsvn_repos/node_tree.c
new file mode 100644
index 0000000..bd947b0
--- /dev/null
+++ b/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/subversion/libsvn_repos/notify.c b/subversion/libsvn_repos/notify.c
new file mode 100644
index 0000000..ac2bca4
--- /dev/null
+++ b/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/subversion/libsvn_repos/replay.c b/subversion/libsvn_repos/replay.c
new file mode 100644
index 0000000..985a673
--- /dev/null
+++ b/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/subversion/libsvn_repos/reporter.c b/subversion/libsvn_repos/reporter.c
new file mode 100644
index 0000000..a9d1eff
--- /dev/null
+++ b/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/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c
new file mode 100644
index 0000000..9f10c06
--- /dev/null
+++ b/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/subversion/libsvn_repos/repos.h b/subversion/libsvn_repos/repos.h
new file mode 100644
index 0000000..fd5b0b4
--- /dev/null
+++ b/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/subversion/libsvn_repos/rev_hunt.c b/subversion/libsvn_repos/rev_hunt.c
new file mode 100644
index 0000000..77b1f2a
--- /dev/null
+++ b/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/subversion/libsvn_subr/adler32.c b/subversion/libsvn_subr/adler32.c
new file mode 100644
index 0000000..e290e68
--- /dev/null
+++ b/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/subversion/libsvn_subr/atomic.c b/subversion/libsvn_subr/atomic.c
new file mode 100644
index 0000000..b760da4
--- /dev/null
+++ b/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/subversion/libsvn_subr/auth.c b/subversion/libsvn_subr/auth.c
new file mode 100644
index 0000000..5406358
--- /dev/null
+++ b/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/subversion/libsvn_subr/auth.h b/subversion/libsvn_subr/auth.h
new file mode 100644
index 0000000..0885f6d
--- /dev/null
+++ b/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/subversion/libsvn_subr/base64.c b/subversion/libsvn_subr/base64.c
new file mode 100644
index 0000000..97ee3d2
--- /dev/null
+++ b/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/subversion/libsvn_subr/cache-inprocess.c b/subversion/libsvn_subr/cache-inprocess.c
new file mode 100644
index 0000000..6401f9f
--- /dev/null
+++ b/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/subversion/libsvn_subr/cache-membuffer.c b/subversion/libsvn_subr/cache-membuffer.c
new file mode 100644
index 0000000..5f447b3
--- /dev/null
+++ b/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/subversion/libsvn_subr/cache-memcache.c b/subversion/libsvn_subr/cache-memcache.c
new file mode 100644
index 0000000..5332d04
--- /dev/null
+++ b/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/subversion/libsvn_subr/cache.c b/subversion/libsvn_subr/cache.c
new file mode 100644
index 0000000..70e189f
--- /dev/null
+++ b/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/subversion/libsvn_subr/cache.h b/subversion/libsvn_subr/cache.h
new file mode 100644
index 0000000..5029cef
--- /dev/null
+++ b/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/subversion/libsvn_subr/cache_config.c b/subversion/libsvn_subr/cache_config.c
new file mode 100644
index 0000000..86c12dc
--- /dev/null
+++ b/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/subversion/libsvn_subr/checksum.c b/subversion/libsvn_subr/checksum.c
new file mode 100644
index 0000000..e5d6a62
--- /dev/null
+++ b/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/subversion/libsvn_subr/cmdline.c b/subversion/libsvn_subr/cmdline.c
new file mode 100644
index 0000000..8484c96
--- /dev/null
+++ b/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/subversion/libsvn_subr/compat.c b/subversion/libsvn_subr/compat.c
new file mode 100644
index 0000000..2089828
--- /dev/null
+++ b/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/subversion/libsvn_subr/config.c b/subversion/libsvn_subr/config.c
new file mode 100644
index 0000000..94aecd3
--- /dev/null
+++ b/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/subversion/libsvn_subr/config_auth.c b/subversion/libsvn_subr/config_auth.c
new file mode 100644
index 0000000..d53403c
--- /dev/null
+++ b/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/subversion/libsvn_subr/config_file.c b/subversion/libsvn_subr/config_file.c
new file mode 100644
index 0000000..9d15f6b
--- /dev/null
+++ b/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/subversion/libsvn_subr/config_impl.h b/subversion/libsvn_subr/config_impl.h
new file mode 100644
index 0000000..a3ab8fa
--- /dev/null
+++ b/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/subversion/libsvn_subr/config_win.c b/subversion/libsvn_subr/config_win.c
new file mode 100644
index 0000000..0a15129
--- /dev/null
+++ b/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/subversion/libsvn_subr/crypto.c b/subversion/libsvn_subr/crypto.c
new file mode 100644
index 0000000..f3611a1e
--- /dev/null
+++ b/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/subversion/libsvn_subr/crypto.h b/subversion/libsvn_subr/crypto.h
new file mode 100644
index 0000000..5e7be86
--- /dev/null
+++ b/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/subversion/libsvn_subr/ctype.c b/subversion/libsvn_subr/ctype.c
new file mode 100644
index 0000000..0dd5d5b
--- /dev/null
+++ b/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/subversion/libsvn_subr/date.c b/subversion/libsvn_subr/date.c
new file mode 100644
index 0000000..6035645
--- /dev/null
+++ b/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/subversion/libsvn_subr/debug.c b/subversion/libsvn_subr/debug.c
new file mode 100644
index 0000000..be331ed
--- /dev/null
+++ b/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/subversion/libsvn_subr/deprecated.c b/subversion/libsvn_subr/deprecated.c
new file mode 100644
index 0000000..378b3f8
--- /dev/null
+++ b/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/subversion/libsvn_subr/dirent_uri.c b/subversion/libsvn_subr/dirent_uri.c
new file mode 100644
index 0000000..2b51e7a
--- /dev/null
+++ b/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/subversion/libsvn_subr/dirent_uri.h b/subversion/libsvn_subr/dirent_uri.h
new file mode 100644
index 0000000..660fb34
--- /dev/null
+++ b/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/subversion/libsvn_subr/dso.c b/subversion/libsvn_subr/dso.c
new file mode 100644
index 0000000..3fa2517
--- /dev/null
+++ b/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/subversion/libsvn_subr/eol.c b/subversion/libsvn_subr/eol.c
new file mode 100644
index 0000000..88a6a37
--- /dev/null
+++ b/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/subversion/libsvn_subr/error.c b/subversion/libsvn_subr/error.c
new file mode 100644
index 0000000..2f04320
--- /dev/null
+++ b/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/subversion/libsvn_subr/genctype.py b/subversion/libsvn_subr/genctype.py
new file mode 100755
index 0000000..21638ba
--- /dev/null
+++ b/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/subversion/libsvn_subr/gpg_agent.c b/subversion/libsvn_subr/gpg_agent.c
new file mode 100644
index 0000000..f0395c0
--- /dev/null
+++ b/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/subversion/libsvn_subr/hash.c b/subversion/libsvn_subr/hash.c
new file mode 100644
index 0000000..7868cac
--- /dev/null
+++ b/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/subversion/libsvn_subr/internal_statements.h b/subversion/libsvn_subr/internal_statements.h
new file mode 100644
index 0000000..fc429b3
--- /dev/null
+++ b/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/subversion/libsvn_subr/internal_statements.sql b/subversion/libsvn_subr/internal_statements.sql
new file mode 100644
index 0000000..c78e855
--- /dev/null
+++ b/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/subversion/libsvn_subr/io.c b/subversion/libsvn_subr/io.c
new file mode 100644
index 0000000..58bc540
--- /dev/null
+++ b/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/subversion/libsvn_subr/iter.c b/subversion/libsvn_subr/iter.c
new file mode 100644
index 0000000..45ec489
--- /dev/null
+++ b/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/subversion/libsvn_subr/lock.c b/subversion/libsvn_subr/lock.c
new file mode 100644
index 0000000..71dd8c7
--- /dev/null
+++ b/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/subversion/libsvn_subr/log.c b/subversion/libsvn_subr/log.c
new file mode 100644
index 0000000..9e0b22a
--- /dev/null
+++ b/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/subversion/libsvn_subr/macos_keychain.c b/subversion/libsvn_subr/macos_keychain.c
new file mode 100644
index 0000000..f15324e
--- /dev/null
+++ b/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/subversion/libsvn_subr/magic.c b/subversion/libsvn_subr/magic.c
new file mode 100644
index 0000000..812a263
--- /dev/null
+++ b/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/subversion/libsvn_subr/md5.c b/subversion/libsvn_subr/md5.c
new file mode 100644
index 0000000..a707a71
--- /dev/null
+++ b/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/subversion/libsvn_subr/md5.h b/subversion/libsvn_subr/md5.h
new file mode 100644
index 0000000..0d83539
--- /dev/null
+++ b/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/subversion/libsvn_subr/mergeinfo.c b/subversion/libsvn_subr/mergeinfo.c
new file mode 100644
index 0000000..63496ae
--- /dev/null
+++ b/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/subversion/libsvn_subr/mutex.c b/subversion/libsvn_subr/mutex.c
new file mode 100644
index 0000000..04988eb
--- /dev/null
+++ b/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/subversion/libsvn_subr/named_atomic.c b/subversion/libsvn_subr/named_atomic.c
new file mode 100644
index 0000000..d07e742
--- /dev/null
+++ b/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/subversion/libsvn_subr/nls.c b/subversion/libsvn_subr/nls.c
new file mode 100644
index 0000000..b026e39
--- /dev/null
+++ b/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/subversion/libsvn_subr/opt.c b/subversion/libsvn_subr/opt.c
new file mode 100644
index 0000000..28ffed1
--- /dev/null
+++ b/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/subversion/libsvn_subr/opt.h b/subversion/libsvn_subr/opt.h
new file mode 100644
index 0000000..ddf3984
--- /dev/null
+++ b/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/subversion/libsvn_subr/path.c b/subversion/libsvn_subr/path.c
new file mode 100644
index 0000000..84368f3
--- /dev/null
+++ b/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/subversion/libsvn_subr/pool.c b/subversion/libsvn_subr/pool.c
new file mode 100644
index 0000000..179ef79
--- /dev/null
+++ b/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/subversion/libsvn_subr/prompt.c b/subversion/libsvn_subr/prompt.c
new file mode 100644
index 0000000..92ee6a2
--- /dev/null
+++ b/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/subversion/libsvn_subr/properties.c b/subversion/libsvn_subr/properties.c
new file mode 100644
index 0000000..738d00f
--- /dev/null
+++ b/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/subversion/libsvn_subr/pseudo_md5.c b/subversion/libsvn_subr/pseudo_md5.c
new file mode 100644
index 0000000..8c194f7
--- /dev/null
+++ b/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/subversion/libsvn_subr/quoprint.c b/subversion/libsvn_subr/quoprint.c
new file mode 100644
index 0000000..bb9869d
--- /dev/null
+++ b/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/subversion/libsvn_subr/sha1.c b/subversion/libsvn_subr/sha1.c
new file mode 100644
index 0000000..45470cb
--- /dev/null
+++ b/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/subversion/libsvn_subr/sha1.h b/subversion/libsvn_subr/sha1.h
new file mode 100644
index 0000000..976810b
--- /dev/null
+++ b/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/subversion/libsvn_subr/simple_providers.c b/subversion/libsvn_subr/simple_providers.c
new file mode 100644
index 0000000..e70770a
--- /dev/null
+++ b/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/subversion/libsvn_subr/skel.c b/subversion/libsvn_subr/skel.c
new file mode 100644
index 0000000..ed12db0
--- /dev/null
+++ b/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/subversion/libsvn_subr/sorts.c b/subversion/libsvn_subr/sorts.c
new file mode 100644
index 0000000..bdec8e4
--- /dev/null
+++ b/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/subversion/libsvn_subr/spillbuf.c b/subversion/libsvn_subr/spillbuf.c
new file mode 100644
index 0000000..e028741
--- /dev/null
+++ b/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/subversion/libsvn_subr/sqlite.c b/subversion/libsvn_subr/sqlite.c
new file mode 100644
index 0000000..0afceff
--- /dev/null
+++ b/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/subversion/libsvn_subr/sqlite3wrapper.c b/subversion/libsvn_subr/sqlite3wrapper.c
new file mode 100644
index 0000000..d1941aa
--- /dev/null
+++ b/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/subversion/libsvn_subr/ssl_client_cert_providers.c b/subversion/libsvn_subr/ssl_client_cert_providers.c
new file mode 100644
index 0000000..cf86fa1
--- /dev/null
+++ b/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/subversion/libsvn_subr/ssl_client_cert_pw_providers.c b/subversion/libsvn_subr/ssl_client_cert_pw_providers.c
new file mode 100644
index 0000000..6c1bcf1
--- /dev/null
+++ b/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/subversion/libsvn_subr/ssl_server_trust_providers.c b/subversion/libsvn_subr/ssl_server_trust_providers.c
new file mode 100644
index 0000000..c69be77
--- /dev/null
+++ b/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/subversion/libsvn_subr/stream.c b/subversion/libsvn_subr/stream.c
new file mode 100644
index 0000000..e2529c7
--- /dev/null
+++ b/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/subversion/libsvn_subr/string.c b/subversion/libsvn_subr/string.c
new file mode 100644
index 0000000..20b0f24
--- /dev/null
+++ b/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/subversion/libsvn_subr/subst.c b/subversion/libsvn_subr/subst.c
new file mode 100644
index 0000000..f69dcf8
--- /dev/null
+++ b/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/subversion/libsvn_subr/sysinfo.c b/subversion/libsvn_subr/sysinfo.c
new file mode 100644
index 0000000..455dca4
--- /dev/null
+++ b/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/subversion/libsvn_subr/sysinfo.h b/subversion/libsvn_subr/sysinfo.h
new file mode 100644
index 0000000..6a6e74d
--- /dev/null
+++ b/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/subversion/libsvn_subr/target.c b/subversion/libsvn_subr/target.c
new file mode 100644
index 0000000..3525167
--- /dev/null
+++ b/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/subversion/libsvn_subr/temp_serializer.c b/subversion/libsvn_subr/temp_serializer.c
new file mode 100644
index 0000000..261267a
--- /dev/null
+++ b/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/subversion/libsvn_subr/time.c b/subversion/libsvn_subr/time.c
new file mode 100644
index 0000000..ccd6269
--- /dev/null
+++ b/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/subversion/libsvn_subr/token.c b/subversion/libsvn_subr/token.c
new file mode 100644
index 0000000..d4fc725
--- /dev/null
+++ b/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/subversion/libsvn_subr/types.c b/subversion/libsvn_subr/types.c
new file mode 100644
index 0000000..30ebb65
--- /dev/null
+++ b/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/subversion/libsvn_subr/user.c b/subversion/libsvn_subr/user.c
new file mode 100644
index 0000000..7d6d0bf
--- /dev/null
+++ b/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/subversion/libsvn_subr/username_providers.c b/subversion/libsvn_subr/username_providers.c
new file mode 100644
index 0000000..a6ef5b3
--- /dev/null
+++ b/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/subversion/libsvn_subr/utf.c b/subversion/libsvn_subr/utf.c
new file mode 100644
index 0000000..355e068
--- /dev/null
+++ b/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/subversion/libsvn_subr/utf_validate.c b/subversion/libsvn_subr/utf_validate.c
new file mode 100644
index 0000000..8311fd7
--- /dev/null
+++ b/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/subversion/libsvn_subr/utf_width.c b/subversion/libsvn_subr/utf_width.c
new file mode 100644
index 0000000..3adff4c
--- /dev/null
+++ b/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/subversion/libsvn_subr/validate.c b/subversion/libsvn_subr/validate.c
new file mode 100644
index 0000000..2052339
--- /dev/null
+++ b/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/subversion/libsvn_subr/version.c b/subversion/libsvn_subr/version.c
new file mode 100644
index 0000000..ad2a270
--- /dev/null
+++ b/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/subversion/libsvn_subr/win32_crashrpt.c b/subversion/libsvn_subr/win32_crashrpt.c
new file mode 100644
index 0000000..6becc96
--- /dev/null
+++ b/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/subversion/libsvn_subr/win32_crashrpt.h b/subversion/libsvn_subr/win32_crashrpt.h
new file mode 100644
index 0000000..77c25c1
--- /dev/null
+++ b/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/subversion/libsvn_subr/win32_crashrpt_dll.h b/subversion/libsvn_subr/win32_crashrpt_dll.h
new file mode 100644
index 0000000..18a4fc9
--- /dev/null
+++ b/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/subversion/libsvn_subr/win32_crypto.c b/subversion/libsvn_subr/win32_crypto.c
new file mode 100644
index 0000000..a7e3828
--- /dev/null
+++ b/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/subversion/libsvn_subr/win32_xlate.c b/subversion/libsvn_subr/win32_xlate.c
new file mode 100644
index 0000000..efe9c05
--- /dev/null
+++ b/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/subversion/libsvn_subr/win32_xlate.h b/subversion/libsvn_subr/win32_xlate.h
new file mode 100644
index 0000000..82fc832
--- /dev/null
+++ b/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/subversion/libsvn_subr/xml.c b/subversion/libsvn_subr/xml.c
new file mode 100644
index 0000000..a9d834a
--- /dev/null
+++ b/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/subversion/libsvn_wc/README b/subversion/libsvn_wc/README
new file mode 100644
index 0000000..b5fc529
--- /dev/null
+++ b/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/subversion/libsvn_wc/adm_crawler.c b/subversion/libsvn_wc/adm_crawler.c
new file mode 100644
index 0000000..e5935a2
--- /dev/null
+++ b/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/subversion/libsvn_wc/adm_files.c b/subversion/libsvn_wc/adm_files.c
new file mode 100644
index 0000000..11ad277
--- /dev/null
+++ b/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/subversion/libsvn_wc/adm_files.h b/subversion/libsvn_wc/adm_files.h
new file mode 100644
index 0000000..3712149
--- /dev/null
+++ b/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/subversion/libsvn_wc/adm_ops.c b/subversion/libsvn_wc/adm_ops.c
new file mode 100644
index 0000000..1f391fc
--- /dev/null
+++ b/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/subversion/libsvn_wc/ambient_depth_filter_editor.c b/subversion/libsvn_wc/ambient_depth_filter_editor.c
new file mode 100644
index 0000000..ff9a5c3
--- /dev/null
+++ b/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/subversion/libsvn_wc/cleanup.c b/subversion/libsvn_wc/cleanup.c
new file mode 100644
index 0000000..8ffb87e
--- /dev/null
+++ b/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/subversion/libsvn_wc/conflicts.c b/subversion/libsvn_wc/conflicts.c
new file mode 100644
index 0000000..7a49188
--- /dev/null
+++ b/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/subversion/libsvn_wc/conflicts.h b/subversion/libsvn_wc/conflicts.h
new file mode 100644
index 0000000..d473065
--- /dev/null
+++ b/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/subversion/libsvn_wc/context.c b/subversion/libsvn_wc/context.c
new file mode 100644
index 0000000..4bf2369
--- /dev/null
+++ b/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/subversion/libsvn_wc/copy.c b/subversion/libsvn_wc/copy.c
new file mode 100644
index 0000000..1b82c2d
--- /dev/null
+++ b/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/subversion/libsvn_wc/crop.c b/subversion/libsvn_wc/crop.c
new file mode 100644
index 0000000..2278476
--- /dev/null
+++ b/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/subversion/libsvn_wc/delete.c b/subversion/libsvn_wc/delete.c
new file mode 100644
index 0000000..37c8af0
--- /dev/null
+++ b/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/subversion/libsvn_wc/deprecated.c b/subversion/libsvn_wc/deprecated.c
new file mode 100644
index 0000000..79cdb30
--- /dev/null
+++ b/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/subversion/libsvn_wc/diff.h b/subversion/libsvn_wc/diff.h
new file mode 100644
index 0000000..d16a9e5
--- /dev/null
+++ b/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/subversion/libsvn_wc/diff_editor.c b/subversion/libsvn_wc/diff_editor.c
new file mode 100644
index 0000000..839241f
--- /dev/null
+++ b/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/subversion/libsvn_wc/diff_local.c b/subversion/libsvn_wc/diff_local.c
new file mode 100644
index 0000000..ad87c76
--- /dev/null
+++ b/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/subversion/libsvn_wc/entries.c b/subversion/libsvn_wc/entries.c
new file mode 100644
index 0000000..f6a73bf
--- /dev/null
+++ b/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/subversion/libsvn_wc/entries.h b/subversion/libsvn_wc/entries.h
new file mode 100644
index 0000000..87caa46
--- /dev/null
+++ b/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/subversion/libsvn_wc/externals.c b/subversion/libsvn_wc/externals.c
new file mode 100644
index 0000000..514148fe
--- /dev/null
+++ b/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/subversion/libsvn_wc/info.c b/subversion/libsvn_wc/info.c
new file mode 100644
index 0000000..4a37e00
--- /dev/null
+++ b/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/subversion/libsvn_wc/lock.c b/subversion/libsvn_wc/lock.c
new file mode 100644
index 0000000..36fbb0e
--- /dev/null
+++ b/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/subversion/libsvn_wc/lock.h b/subversion/libsvn_wc/lock.h
new file mode 100644
index 0000000..e015c7e
--- /dev/null
+++ b/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/subversion/libsvn_wc/merge.c b/subversion/libsvn_wc/merge.c
new file mode 100644
index 0000000..7cff3e4
--- /dev/null
+++ b/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/subversion/libsvn_wc/node.c b/subversion/libsvn_wc/node.c
new file mode 100644
index 0000000..a1d6b02
--- /dev/null
+++ b/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/subversion/libsvn_wc/old-and-busted.c b/subversion/libsvn_wc/old-and-busted.c
new file mode 100644
index 0000000..20f7c6c
--- /dev/null
+++ b/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/subversion/libsvn_wc/props.c b/subversion/libsvn_wc/props.c
new file mode 100644
index 0000000..a7b2339
--- /dev/null
+++ b/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/subversion/libsvn_wc/props.h b/subversion/libsvn_wc/props.h
new file mode 100644
index 0000000..c648e3c
--- /dev/null
+++ b/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/subversion/libsvn_wc/questions.c b/subversion/libsvn_wc/questions.c
new file mode 100644
index 0000000..c2a42b6
--- /dev/null
+++ b/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/subversion/libsvn_wc/relocate.c b/subversion/libsvn_wc/relocate.c
new file mode 100644
index 0000000..4a9df67
--- /dev/null
+++ b/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/subversion/libsvn_wc/revert.c b/subversion/libsvn_wc/revert.c
new file mode 100644
index 0000000..5e190e8
--- /dev/null
+++ b/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/subversion/libsvn_wc/revision_status.c b/subversion/libsvn_wc/revision_status.c
new file mode 100644
index 0000000..a4b9bea
--- /dev/null
+++ b/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/subversion/libsvn_wc/status.c b/subversion/libsvn_wc/status.c
new file mode 100644
index 0000000..1440b2e
--- /dev/null
+++ b/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/subversion/libsvn_wc/token-map.h b/subversion/libsvn_wc/token-map.h
new file mode 100644
index 0000000..9da12b8
--- /dev/null
+++ b/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/subversion/libsvn_wc/translate.c b/subversion/libsvn_wc/translate.c
new file mode 100644
index 0000000..9e0b265
--- /dev/null
+++ b/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/subversion/libsvn_wc/translate.h b/subversion/libsvn_wc/translate.h
new file mode 100644
index 0000000..c5203be
--- /dev/null
+++ b/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/subversion/libsvn_wc/tree_conflicts.c b/subversion/libsvn_wc/tree_conflicts.c
new file mode 100644
index 0000000..4445c96
--- /dev/null
+++ b/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/subversion/libsvn_wc/tree_conflicts.h b/subversion/libsvn_wc/tree_conflicts.h
new file mode 100644
index 0000000..68cd9f6
--- /dev/null
+++ b/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/subversion/libsvn_wc/update_editor.c b/subversion/libsvn_wc/update_editor.c
new file mode 100644
index 0000000..617ad47
--- /dev/null
+++ b/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/subversion/libsvn_wc/upgrade.c b/subversion/libsvn_wc/upgrade.c
new file mode 100644
index 0000000..983892c
--- /dev/null
+++ b/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/subversion/libsvn_wc/util.c b/subversion/libsvn_wc/util.c
new file mode 100644
index 0000000..a527eda
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc-checks.h b/subversion/libsvn_wc/wc-checks.h
new file mode 100644
index 0000000..470460e
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc-checks.sql b/subversion/libsvn_wc/wc-checks.sql
new file mode 100644
index 0000000..a677270
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc-metadata.h b/subversion/libsvn_wc/wc-metadata.h
new file mode 100644
index 0000000..a0d6965
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc-metadata.sql b/subversion/libsvn_wc/wc-metadata.sql
new file mode 100644
index 0000000..d2a6161
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc-queries.h b/subversion/libsvn_wc/wc-queries.h
new file mode 100644
index 0000000..19c709e
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc-queries.sql b/subversion/libsvn_wc/wc-queries.sql
new file mode 100644
index 0000000..0ffe6f0
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc.h b/subversion/libsvn_wc/wc.h
new file mode 100644
index 0000000..9438e2b
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc_db.c b/subversion/libsvn_wc/wc_db.c
new file mode 100644
index 0000000..7e1e877
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc_db.h b/subversion/libsvn_wc/wc_db.h
new file mode 100644
index 0000000..154262d
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc_db_pristine.c b/subversion/libsvn_wc/wc_db_pristine.c
new file mode 100644
index 0000000..d9dc8f3
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc_db_private.h b/subversion/libsvn_wc/wc_db_private.h
new file mode 100644
index 0000000..0679b32
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc_db_update_move.c b/subversion/libsvn_wc/wc_db_update_move.c
new file mode 100644
index 0000000..fa5afe4
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc_db_util.c b/subversion/libsvn_wc/wc_db_util.c
new file mode 100644
index 0000000..39dd034
--- /dev/null
+++ b/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/subversion/libsvn_wc/wc_db_wcroot.c b/subversion/libsvn_wc/wc_db_wcroot.c
new file mode 100644
index 0000000..1091f1b
--- /dev/null
+++ b/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/subversion/libsvn_wc/wcroot_anchor.c b/subversion/libsvn_wc/wcroot_anchor.c
new file mode 100644
index 0000000..913a61b
--- /dev/null
+++ b/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/subversion/libsvn_wc/workqueue.c b/subversion/libsvn_wc/workqueue.c
new file mode 100644
index 0000000..ddbac15
--- /dev/null
+++ b/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/subversion/libsvn_wc/workqueue.h b/subversion/libsvn_wc/workqueue.h
new file mode 100644
index 0000000..0617a60
--- /dev/null
+++ b/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/subversion/svn/add-cmd.c b/subversion/svn/add-cmd.c
new file mode 100644
index 0000000..44f73c7
--- /dev/null
+++ b/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/subversion/svn/blame-cmd.c b/subversion/svn/blame-cmd.c
new file mode 100644
index 0000000..174a199
--- /dev/null
+++ b/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/subversion/svn/cat-cmd.c b/subversion/svn/cat-cmd.c
new file mode 100644
index 0000000..551420e
--- /dev/null
+++ b/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/subversion/svn/changelist-cmd.c b/subversion/svn/changelist-cmd.c
new file mode 100644
index 0000000..46347b6
--- /dev/null
+++ b/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/subversion/svn/checkout-cmd.c b/subversion/svn/checkout-cmd.c
new file mode 100644
index 0000000..6c192a0
--- /dev/null
+++ b/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/subversion/svn/cl-conflicts.c b/subversion/svn/cl-conflicts.c
new file mode 100644
index 0000000..440c9d7
--- /dev/null
+++ b/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/subversion/svn/cl-conflicts.h b/subversion/svn/cl-conflicts.h
new file mode 100644
index 0000000..07591a0
--- /dev/null
+++ b/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/subversion/svn/cl.h b/subversion/svn/cl.h
new file mode 100644
index 0000000..f7ebee6
--- /dev/null
+++ b/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/subversion/svn/cleanup-cmd.c b/subversion/svn/cleanup-cmd.c
new file mode 100644
index 0000000..64fa400
--- /dev/null
+++ b/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/subversion/svn/client_errors.h b/subversion/svn/client_errors.h
new file mode 100644
index 0000000..19f0bdf
--- /dev/null
+++ b/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/subversion/svn/commit-cmd.c b/subversion/svn/commit-cmd.c
new file mode 100644
index 0000000..2d04c69
--- /dev/null
+++ b/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/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c
new file mode 100644
index 0000000..096a189
--- /dev/null
+++ b/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/subversion/svn/copy-cmd.c b/subversion/svn/copy-cmd.c
new file mode 100644
index 0000000..e6fbd4b
--- /dev/null
+++ b/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/subversion/svn/delete-cmd.c b/subversion/svn/delete-cmd.c
new file mode 100644
index 0000000..e73813b
--- /dev/null
+++ b/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/subversion/svn/deprecated.c b/subversion/svn/deprecated.c
new file mode 100644
index 0000000..6115573
--- /dev/null
+++ b/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/subversion/svn/diff-cmd.c b/subversion/svn/diff-cmd.c
new file mode 100644
index 0000000..2cbd202
--- /dev/null
+++ b/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/subversion/svn/export-cmd.c b/subversion/svn/export-cmd.c
new file mode 100644
index 0000000..75b6723
--- /dev/null
+++ b/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/subversion/svn/file-merge.c b/subversion/svn/file-merge.c
new file mode 100644
index 0000000..43ebba9
--- /dev/null
+++ b/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/subversion/svn/help-cmd.c b/subversion/svn/help-cmd.c
new file mode 100644
index 0000000..93fecc3
--- /dev/null
+++ b/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/subversion/svn/import-cmd.c b/subversion/svn/import-cmd.c
new file mode 100644
index 0000000..6fe5af6
--- /dev/null
+++ b/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/subversion/svn/info-cmd.c b/subversion/svn/info-cmd.c
new file mode 100644
index 0000000..56833f6
--- /dev/null
+++ b/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/subversion/svn/list-cmd.c b/subversion/svn/list-cmd.c
new file mode 100644
index 0000000..efe4279
--- /dev/null
+++ b/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/subversion/svn/lock-cmd.c b/subversion/svn/lock-cmd.c
new file mode 100644
index 0000000..c2795da
--- /dev/null
+++ b/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/subversion/svn/log-cmd.c b/subversion/svn/log-cmd.c
new file mode 100644
index 0000000..af57cf4
--- /dev/null
+++ b/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/subversion/svn/merge-cmd.c b/subversion/svn/merge-cmd.c
new file mode 100644
index 0000000..c14f769
--- /dev/null
+++ b/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/subversion/svn/mergeinfo-cmd.c b/subversion/svn/mergeinfo-cmd.c
new file mode 100644
index 0000000..a78c42a
--- /dev/null
+++ b/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/subversion/svn/mkdir-cmd.c b/subversion/svn/mkdir-cmd.c
new file mode 100644
index 0000000..64cb4f9
--- /dev/null
+++ b/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/subversion/svn/move-cmd.c b/subversion/svn/move-cmd.c
new file mode 100644
index 0000000..bb71043
--- /dev/null
+++ b/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/subversion/svn/notify.c b/subversion/svn/notify.c
new file mode 100644
index 0000000..6498fb1
--- /dev/null
+++ b/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/subversion/svn/patch-cmd.c b/subversion/svn/patch-cmd.c
new file mode 100644
index 0000000..83707c6
--- /dev/null
+++ b/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/subversion/svn/propdel-cmd.c b/subversion/svn/propdel-cmd.c
new file mode 100644
index 0000000..28c9597
--- /dev/null
+++ b/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/subversion/svn/propedit-cmd.c b/subversion/svn/propedit-cmd.c
new file mode 100644
index 0000000..520fe6c
--- /dev/null
+++ b/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/subversion/svn/propget-cmd.c b/subversion/svn/propget-cmd.c
new file mode 100644
index 0000000..e291911
--- /dev/null
+++ b/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/subversion/svn/proplist-cmd.c b/subversion/svn/proplist-cmd.c
new file mode 100644
index 0000000..fe23a67
--- /dev/null
+++ b/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/subversion/svn/props.c b/subversion/svn/props.c
new file mode 100644
index 0000000..2a41ac8
--- /dev/null
+++ b/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/subversion/svn/propset-cmd.c b/subversion/svn/propset-cmd.c
new file mode 100644
index 0000000..07b9bbd
--- /dev/null
+++ b/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/subversion/svn/relocate-cmd.c b/subversion/svn/relocate-cmd.c
new file mode 100644
index 0000000..fe50f66
--- /dev/null
+++ b/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/subversion/svn/resolve-cmd.c b/subversion/svn/resolve-cmd.c
new file mode 100644
index 0000000..ce4818e
--- /dev/null
+++ b/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/subversion/svn/resolved-cmd.c b/subversion/svn/resolved-cmd.c
new file mode 100644
index 0000000..51e2da17
--- /dev/null
+++ b/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/subversion/svn/revert-cmd.c b/subversion/svn/revert-cmd.c
new file mode 100644
index 0000000..d17aeb6
--- /dev/null
+++ b/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/subversion/svn/schema/blame.rnc b/subversion/svn/schema/blame.rnc
new file mode 100644
index 0000000..b6a1e41
--- /dev/null
+++ b/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/subversion/svn/schema/common.rnc b/subversion/svn/schema/common.rnc
new file mode 100644
index 0000000..95729e3
--- /dev/null
+++ b/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/subversion/svn/schema/diff.rnc b/subversion/svn/schema/diff.rnc
new file mode 100644
index 0000000..ab89b81
--- /dev/null
+++ b/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/subversion/svn/schema/info.rnc b/subversion/svn/schema/info.rnc
new file mode 100644
index 0000000..3dc43f6
--- /dev/null
+++ b/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/subversion/svn/schema/list.rnc b/subversion/svn/schema/list.rnc
new file mode 100644
index 0000000..13d5897
--- /dev/null
+++ b/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/subversion/svn/schema/log.rnc b/subversion/svn/schema/log.rnc
new file mode 100644
index 0000000..14a8b7e
--- /dev/null
+++ b/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/subversion/svn/schema/props.rnc b/subversion/svn/schema/props.rnc
new file mode 100644
index 0000000..260c93e
--- /dev/null
+++ b/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/subversion/svn/schema/status.rnc b/subversion/svn/schema/status.rnc
new file mode 100644
index 0000000..73d0ca0
--- /dev/null
+++ b/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/subversion/svn/status-cmd.c b/subversion/svn/status-cmd.c
new file mode 100644
index 0000000..0a73dac
--- /dev/null
+++ b/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/subversion/svn/status.c b/subversion/svn/status.c
new file mode 100644
index 0000000..3679bff
--- /dev/null
+++ b/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/subversion/svn/svn.1 b/subversion/svn/svn.1
new file mode 100644
index 0000000..f4ad251
--- /dev/null
+++ b/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/subversion/svn/svn.c b/subversion/svn/svn.c
new file mode 100644
index 0000000..cbcec87
--- /dev/null
+++ b/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/subversion/svn/switch-cmd.c b/subversion/svn/switch-cmd.c
new file mode 100644
index 0000000..aaef2b5
--- /dev/null
+++ b/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/subversion/svn/unlock-cmd.c b/subversion/svn/unlock-cmd.c
new file mode 100644
index 0000000..0f94d2a
--- /dev/null
+++ b/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/subversion/svn/update-cmd.c b/subversion/svn/update-cmd.c
new file mode 100644
index 0000000..77c28f9
--- /dev/null
+++ b/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/subversion/svn/upgrade-cmd.c b/subversion/svn/upgrade-cmd.c
new file mode 100644
index 0000000..e2df143
--- /dev/null
+++ b/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/subversion/svn/util.c b/subversion/svn/util.c
new file mode 100644
index 0000000..5d386f8
--- /dev/null
+++ b/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/subversion/svn_private_config.h.in b/subversion/svn_private_config.h.in
new file mode 100644
index 0000000..84d157b
--- /dev/null
+++ b/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/subversion/svn_private_config.hw b/subversion/svn_private_config.hw
new file mode 100644
index 0000000..61517f9
--- /dev/null
+++ b/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/subversion/svnadmin/svnadmin.1 b/subversion/svnadmin/svnadmin.1
new file mode 100644
index 0000000..c613bb1
--- /dev/null
+++ b/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/subversion/svnadmin/svnadmin.c b/subversion/svnadmin/svnadmin.c
new file mode 100644
index 0000000..65ff971
--- /dev/null
+++ b/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/subversion/svndumpfilter/svndumpfilter.1 b/subversion/svndumpfilter/svndumpfilter.1
new file mode 100644
index 0000000..c8c00fe
--- /dev/null
+++ b/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/subversion/svndumpfilter/svndumpfilter.c b/subversion/svndumpfilter/svndumpfilter.c
new file mode 100644
index 0000000..dd4e4ab
--- /dev/null
+++ b/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/subversion/svnlook/svnlook.1 b/subversion/svnlook/svnlook.1
new file mode 100644
index 0000000..22ded4c
--- /dev/null
+++ b/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/subversion/svnlook/svnlook.c b/subversion/svnlook/svnlook.c
new file mode 100644
index 0000000..e619450
--- /dev/null
+++ b/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/subversion/svnmucc/svnmucc.1 b/subversion/svnmucc/svnmucc.1
new file mode 100644
index 0000000..2b9ae67
--- /dev/null
+++ b/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/subversion/svnmucc/svnmucc.c b/subversion/svnmucc/svnmucc.c
new file mode 100644
index 0000000..076a9ee
--- /dev/null
+++ b/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/subversion/svnrdump/dump_editor.c b/subversion/svnrdump/dump_editor.c
new file mode 100644
index 0000000..faf029f
--- /dev/null
+++ b/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/subversion/svnrdump/load_editor.c b/subversion/svnrdump/load_editor.c
new file mode 100644
index 0000000..1b053f2
--- /dev/null
+++ b/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/subversion/svnrdump/svnrdump.1 b/subversion/svnrdump/svnrdump.1
new file mode 100644
index 0000000..99f5210
--- /dev/null
+++ b/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/subversion/svnrdump/svnrdump.c b/subversion/svnrdump/svnrdump.c
new file mode 100644
index 0000000..6bf409c
--- /dev/null
+++ b/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/subversion/svnrdump/svnrdump.h b/subversion/svnrdump/svnrdump.h
new file mode 100644
index 0000000..2a81014
--- /dev/null
+++ b/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/subversion/svnrdump/util.c b/subversion/svnrdump/util.c
new file mode 100644
index 0000000..91cefb3
--- /dev/null
+++ b/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/subversion/svnserve/cyrus_auth.c b/subversion/svnserve/cyrus_auth.c
new file mode 100644
index 0000000..2d75047
--- /dev/null
+++ b/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/subversion/svnserve/log-escape.c b/subversion/svnserve/log-escape.c
new file mode 100644
index 0000000..79f5312
--- /dev/null
+++ b/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/subversion/svnserve/serve.c b/subversion/svnserve/serve.c
new file mode 100644
index 0000000..9e49bdd
--- /dev/null
+++ b/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/subversion/svnserve/server.h b/subversion/svnserve/server.h
new file mode 100644
index 0000000..926a96f
--- /dev/null
+++ b/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/subversion/svnserve/svnserve.8 b/subversion/svnserve/svnserve.8
new file mode 100644
index 0000000..deea846
--- /dev/null
+++ b/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/subversion/svnserve/svnserve.c b/subversion/svnserve/svnserve.c
new file mode 100644
index 0000000..7648ea8
--- /dev/null
+++ b/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/subversion/svnserve/svnserve.conf.5 b/subversion/svnserve/svnserve.conf.5
new file mode 100644
index 0000000..f86aeb3
--- /dev/null
+++ b/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/subversion/svnserve/winservice.c b/subversion/svnserve/winservice.c
new file mode 100644
index 0000000..dcb399e
--- /dev/null
+++ b/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/subversion/svnserve/winservice.h b/subversion/svnserve/winservice.h
new file mode 100644
index 0000000..8e5ac52
--- /dev/null
+++ b/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/subversion/svnsync/svnsync.1 b/subversion/svnsync/svnsync.1
new file mode 100644
index 0000000..d9da6be
--- /dev/null
+++ b/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/subversion/svnsync/svnsync.c b/subversion/svnsync/svnsync.c
new file mode 100644
index 0000000..0bdc976
--- /dev/null
+++ b/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/subversion/svnsync/sync.c b/subversion/svnsync/sync.c
new file mode 100644
index 0000000..5d86ac2
--- /dev/null
+++ b/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/subversion/svnsync/sync.h b/subversion/svnsync/sync.h
new file mode 100644
index 0000000..8afc263
--- /dev/null
+++ b/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/subversion/svnversion/svnversion.1 b/subversion/svnversion/svnversion.1
new file mode 100644
index 0000000..20edaa5
--- /dev/null
+++ b/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/subversion/svnversion/svnversion.c b/subversion/svnversion/svnversion.c
new file mode 100644
index 0000000..ffee60c
--- /dev/null
+++ b/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;
+}
OpenPOWER on IntegriCloud