From d25dac7fcc6acc838b71bbda8916fd9665c709ab Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 18 Jun 2013 02:07:41 +0000 Subject: Import trimmed svn-1.8.0-rc3 --- subversion/include/mod_authz_svn.h | 61 + subversion/include/mod_dav_svn.h | 99 + subversion/include/private/README | 4 + subversion/include/private/ra_svn_sasl.h | 86 + subversion/include/private/svn_adler32.h | 52 + subversion/include/private/svn_atomic.h | 123 + subversion/include/private/svn_auth_private.h | 220 + subversion/include/private/svn_cache.h | 486 + subversion/include/private/svn_client_private.h | 299 + subversion/include/private/svn_cmdline_private.h | 228 + subversion/include/private/svn_dav_protocol.h | 68 + subversion/include/private/svn_debug.h | 107 + subversion/include/private/svn_delta_private.h | 128 + subversion/include/private/svn_dep_compat.h | 184 + subversion/include/private/svn_diff_private.h | 115 + subversion/include/private/svn_diff_tree.h | 357 + subversion/include/private/svn_doxygen.h | 32 + subversion/include/private/svn_editor.h | 1194 ++ subversion/include/private/svn_eol_private.h | 93 + subversion/include/private/svn_error_private.h | 54 + subversion/include/private/svn_fs_private.h | 189 + subversion/include/private/svn_fs_util.h | 217 + subversion/include/private/svn_fspath.h | 175 + subversion/include/private/svn_io_private.h | 99 + subversion/include/private/svn_log.h | 260 + subversion/include/private/svn_magic.h | 55 + subversion/include/private/svn_mergeinfo_private.h | 270 + subversion/include/private/svn_mutex.h | 117 + subversion/include/private/svn_named_atomic.h | 162 + subversion/include/private/svn_opt_private.h | 156 + subversion/include/private/svn_pseudo_md5.h | 83 + subversion/include/private/svn_ra_private.h | 280 + subversion/include/private/svn_ra_svn_private.h | 826 + subversion/include/private/svn_repos_private.h | 121 + subversion/include/private/svn_skel.h | 236 + subversion/include/private/svn_sqlite.h | 519 + subversion/include/private/svn_string_private.h | 222 + subversion/include/private/svn_subr_private.h | 340 + subversion/include/private/svn_temp_serializer.h | 207 + subversion/include/private/svn_token.h | 98 + subversion/include/private/svn_utf_private.h | 87 + subversion/include/private/svn_wc_private.h | 1847 +++ subversion/include/svn_auth.h | 1282 ++ subversion/include/svn_base64.h | 123 + subversion/include/svn_cache_config.h | 90 + subversion/include/svn_checksum.h | 278 + subversion/include/svn_client.h | 6475 ++++++++ subversion/include/svn_cmdline.h | 376 + subversion/include/svn_compat.h | 104 + subversion/include/svn_config.h | 808 + subversion/include/svn_ctype.h | 196 + subversion/include/svn_dav.h | 398 + subversion/include/svn_delta.h | 1367 ++ subversion/include/svn_diff.h | 1118 ++ subversion/include/svn_dirent_uri.h | 805 + subversion/include/svn_dso.h | 99 + subversion/include/svn_error.h | 662 + subversion/include/svn_error_codes.h | 1521 ++ subversion/include/svn_fs.h | 2530 ++++ subversion/include/svn_hash.h | 265 + subversion/include/svn_io.h | 2282 +++ subversion/include/svn_iter.h | 139 + subversion/include/svn_md5.h | 91 + subversion/include/svn_mergeinfo.h | 612 + subversion/include/svn_nls.h | 56 + subversion/include/svn_opt.h | 779 + subversion/include/svn_path.h | 734 + subversion/include/svn_pools.h | 114 + subversion/include/svn_props.h | 714 + subversion/include/svn_quoprint.h | 77 + subversion/include/svn_ra.h | 2468 +++ subversion/include/svn_ra_svn.h | 668 + subversion/include/svn_repos.h | 3406 +++++ subversion/include/svn_sorts.h | 223 + subversion/include/svn_string.h | 577 + subversion/include/svn_subst.h | 708 + subversion/include/svn_time.h | 94 + subversion/include/svn_types.h | 1287 ++ subversion/include/svn_user.h | 56 + subversion/include/svn_utf.h | 252 + subversion/include/svn_version.h | 411 + subversion/include/svn_wc.h | 8182 ++++++++++ subversion/include/svn_xml.h | 381 + .../libsvn_auth_gnome_keyring/gnome_keyring.c | 517 + subversion/libsvn_auth_gnome_keyring/version.c | 35 + subversion/libsvn_auth_kwallet/kwallet.cpp | 458 + subversion/libsvn_auth_kwallet/version.c | 35 + subversion/libsvn_client/add.c | 1326 ++ subversion/libsvn_client/blame.c | 837 ++ subversion/libsvn_client/cat.c | 308 + subversion/libsvn_client/changelist.c | 144 + subversion/libsvn_client/checkout.c | 198 + subversion/libsvn_client/cleanup.c | 63 + subversion/libsvn_client/client.h | 1124 ++ subversion/libsvn_client/cmdline.c | 363 + subversion/libsvn_client/commit.c | 1031 ++ subversion/libsvn_client/commit_util.c | 1981 +++ subversion/libsvn_client/compat_providers.c | 136 + subversion/libsvn_client/copy.c | 2422 +++ subversion/libsvn_client/copy_foreign.c | 571 + subversion/libsvn_client/ctx.c | 112 + subversion/libsvn_client/delete.c | 595 + subversion/libsvn_client/deprecated.c | 2966 ++++ subversion/libsvn_client/diff.c | 2723 ++++ subversion/libsvn_client/diff_local.c | 633 + subversion/libsvn_client/diff_summarize.c | 317 + subversion/libsvn_client/export.c | 1589 ++ subversion/libsvn_client/externals.c | 1139 ++ subversion/libsvn_client/import.c | 964 ++ subversion/libsvn_client/info.c | 402 + subversion/libsvn_client/iprops.c | 270 + subversion/libsvn_client/list.c | 579 + subversion/libsvn_client/locking_commands.c | 552 + subversion/libsvn_client/log.c | 868 ++ subversion/libsvn_client/merge.c | 12674 ++++++++++++++++ subversion/libsvn_client/mergeinfo.c | 2191 +++ subversion/libsvn_client/mergeinfo.h | 414 + subversion/libsvn_client/patch.c | 3043 ++++ subversion/libsvn_client/prop_commands.c | 1559 ++ subversion/libsvn_client/ra.c | 1147 ++ subversion/libsvn_client/relocate.c | 289 + subversion/libsvn_client/repos_diff.c | 1405 ++ subversion/libsvn_client/resolved.c | 148 + subversion/libsvn_client/revert.c | 201 + subversion/libsvn_client/revisions.c | 191 + subversion/libsvn_client/status.c | 767 + subversion/libsvn_client/switch.c | 487 + subversion/libsvn_client/update.c | 707 + subversion/libsvn_client/upgrade.c | 327 + subversion/libsvn_client/url.c | 63 + subversion/libsvn_client/util.c | 457 + subversion/libsvn_client/version.c | 33 + subversion/libsvn_delta/cancel.c | 378 + subversion/libsvn_delta/compat.c | 2010 +++ subversion/libsvn_delta/compose_delta.c | 837 ++ subversion/libsvn_delta/debug_editor.c | 437 + subversion/libsvn_delta/debug_editor.h | 49 + subversion/libsvn_delta/default_editor.c | 161 + subversion/libsvn_delta/delta.h | 96 + subversion/libsvn_delta/deprecated.c | 48 + subversion/libsvn_delta/depth_filter_editor.c | 485 + subversion/libsvn_delta/editor.c | 956 ++ subversion/libsvn_delta/path_driver.c | 298 + subversion/libsvn_delta/svndiff.c | 1103 ++ subversion/libsvn_delta/text_delta.c | 1041 ++ subversion/libsvn_delta/version.c | 33 + subversion/libsvn_delta/xdelta.c | 514 + subversion/libsvn_diff/deprecated.c | 289 + subversion/libsvn_diff/diff.c | 199 + subversion/libsvn_diff/diff.h | 217 + subversion/libsvn_diff/diff3.c | 529 + subversion/libsvn_diff/diff4.c | 314 + subversion/libsvn_diff/diff_file.c | 2414 +++ subversion/libsvn_diff/diff_memory.c | 1161 ++ subversion/libsvn_diff/diff_tree.c | 1705 +++ subversion/libsvn_diff/lcs.c | 375 + subversion/libsvn_diff/parse-diff.c | 1373 ++ subversion/libsvn_diff/token.c | 198 + subversion/libsvn_diff/util.c | 591 + subversion/libsvn_fs/access.c | 105 + subversion/libsvn_fs/editor.c | 850 ++ subversion/libsvn_fs/fs-loader.c | 1602 ++ subversion/libsvn_fs/fs-loader.h | 502 + subversion/libsvn_fs_base/bdb/bdb-err.c | 106 + subversion/libsvn_fs_base/bdb/bdb-err.h | 115 + subversion/libsvn_fs_base/bdb/bdb_compat.c | 34 + subversion/libsvn_fs_base/bdb/bdb_compat.h | 135 + subversion/libsvn_fs_base/bdb/changes-table.c | 457 + subversion/libsvn_fs_base/bdb/changes-table.h | 94 + .../libsvn_fs_base/bdb/checksum-reps-table.c | 208 + .../libsvn_fs_base/bdb/checksum-reps-table.h | 89 + subversion/libsvn_fs_base/bdb/copies-table.c | 210 + subversion/libsvn_fs_base/bdb/copies-table.h | 93 + subversion/libsvn_fs_base/bdb/dbt.c | 170 + subversion/libsvn_fs_base/bdb/dbt.h | 120 + subversion/libsvn_fs_base/bdb/env.c | 719 + subversion/libsvn_fs_base/bdb/env.h | 159 + subversion/libsvn_fs_base/bdb/lock-tokens-table.c | 157 + subversion/libsvn_fs_base/bdb/lock-tokens-table.h | 96 + subversion/libsvn_fs_base/bdb/locks-table.c | 328 + subversion/libsvn_fs_base/bdb/locks-table.h | 110 + .../libsvn_fs_base/bdb/miscellaneous-table.c | 135 + .../libsvn_fs_base/bdb/miscellaneous-table.h | 71 + subversion/libsvn_fs_base/bdb/node-origins-table.c | 145 + subversion/libsvn_fs_base/bdb/node-origins-table.h | 76 + subversion/libsvn_fs_base/bdb/nodes-table.c | 259 + subversion/libsvn_fs_base/bdb/nodes-table.h | 121 + subversion/libsvn_fs_base/bdb/reps-table.c | 204 + subversion/libsvn_fs_base/bdb/reps-table.h | 94 + subversion/libsvn_fs_base/bdb/rev-table.c | 221 + subversion/libsvn_fs_base/bdb/rev-table.h | 85 + subversion/libsvn_fs_base/bdb/strings-table.c | 541 + subversion/libsvn_fs_base/bdb/strings-table.h | 143 + subversion/libsvn_fs_base/bdb/txn-table.c | 325 + subversion/libsvn_fs_base/bdb/txn-table.h | 100 + subversion/libsvn_fs_base/bdb/uuids-table.c | 149 + subversion/libsvn_fs_base/bdb/uuids-table.h | 69 + subversion/libsvn_fs_base/dag.c | 1758 +++ subversion/libsvn_fs_base/dag.h | 587 + subversion/libsvn_fs_base/err.c | 177 + subversion/libsvn_fs_base/err.h | 98 + subversion/libsvn_fs_base/fs.c | 1436 ++ subversion/libsvn_fs_base/fs.h | 357 + subversion/libsvn_fs_base/id.c | 208 + subversion/libsvn_fs_base/id.h | 81 + subversion/libsvn_fs_base/key-gen.c | 131 + subversion/libsvn_fs_base/key-gen.h | 100 + subversion/libsvn_fs_base/lock.c | 594 + subversion/libsvn_fs_base/lock.h | 120 + subversion/libsvn_fs_base/node-rev.c | 126 + subversion/libsvn_fs_base/node-rev.h | 101 + subversion/libsvn_fs_base/notes/TODO | 137 + subversion/libsvn_fs_base/notes/fs-history | 270 + subversion/libsvn_fs_base/notes/structure | 1086 ++ subversion/libsvn_fs_base/reps-strings.c | 1617 ++ subversion/libsvn_fs_base/reps-strings.h | 176 + subversion/libsvn_fs_base/revs-txns.c | 1067 ++ subversion/libsvn_fs_base/revs-txns.h | 231 + subversion/libsvn_fs_base/trail.c | 292 + subversion/libsvn_fs_base/trail.h | 239 + subversion/libsvn_fs_base/tree.c | 5451 +++++++ subversion/libsvn_fs_base/tree.h | 99 + subversion/libsvn_fs_base/util/fs_skels.c | 1515 ++ subversion/libsvn_fs_base/util/fs_skels.h | 177 + subversion/libsvn_fs_base/uuid.c | 116 + subversion/libsvn_fs_base/uuid.h | 49 + subversion/libsvn_fs_fs/caching.c | 692 + subversion/libsvn_fs_fs/dag.c | 1338 ++ subversion/libsvn_fs_fs/dag.h | 581 + subversion/libsvn_fs_fs/fs.c | 456 + subversion/libsvn_fs_fs/fs.h | 523 + subversion/libsvn_fs_fs/fs_fs.c | 11469 ++++++++++++++ subversion/libsvn_fs_fs/fs_fs.h | 575 + subversion/libsvn_fs_fs/id.c | 405 + subversion/libsvn_fs_fs/id.h | 116 + subversion/libsvn_fs_fs/key-gen.c | 159 + subversion/libsvn_fs_fs/key-gen.h | 91 + subversion/libsvn_fs_fs/lock.c | 1079 ++ subversion/libsvn_fs_fs/lock.h | 103 + subversion/libsvn_fs_fs/rep-cache-db.h | 83 + subversion/libsvn_fs_fs/rep-cache-db.sql | 65 + subversion/libsvn_fs_fs/rep-cache.c | 381 + subversion/libsvn_fs_fs/rep-cache.h | 101 + subversion/libsvn_fs_fs/structure | 621 + subversion/libsvn_fs_fs/temp_serializer.c | 1341 ++ subversion/libsvn_fs_fs/temp_serializer.h | 266 + subversion/libsvn_fs_fs/tree.c | 4420 ++++++ subversion/libsvn_fs_fs/tree.h | 98 + subversion/libsvn_fs_util/fs-util.c | 223 + subversion/libsvn_ra/compat.c | 952 ++ subversion/libsvn_ra/debug_reporter.c | 151 + subversion/libsvn_ra/debug_reporter.h | 49 + subversion/libsvn_ra/deprecated.c | 509 + subversion/libsvn_ra/deprecated.h | 60 + subversion/libsvn_ra/editor.c | 339 + subversion/libsvn_ra/ra_loader.c | 1576 ++ subversion/libsvn_ra/ra_loader.h | 562 + subversion/libsvn_ra/util.c | 242 + subversion/libsvn_ra/wrapper_template.h | 512 + subversion/libsvn_ra_local/ra_local.h | 97 + subversion/libsvn_ra_local/ra_plugin.c | 1766 +++ subversion/libsvn_ra_local/split_url.c | 97 + subversion/libsvn_ra_serf/README | 84 + subversion/libsvn_ra_serf/blame.c | 375 + subversion/libsvn_ra_serf/blncache.c | 179 + subversion/libsvn_ra_serf/blncache.h | 90 + subversion/libsvn_ra_serf/commit.c | 2468 +++ subversion/libsvn_ra_serf/get_deleted_rev.c | 178 + subversion/libsvn_ra_serf/getdate.c | 161 + subversion/libsvn_ra_serf/getlocations.c | 201 + subversion/libsvn_ra_serf/getlocationsegments.c | 206 + subversion/libsvn_ra_serf/getlocks.c | 277 + subversion/libsvn_ra_serf/inherited_props.c | 344 + subversion/libsvn_ra_serf/locks.c | 654 + subversion/libsvn_ra_serf/log.c | 604 + subversion/libsvn_ra_serf/merge.c | 430 + subversion/libsvn_ra_serf/mergeinfo.c | 246 + subversion/libsvn_ra_serf/options.c | 625 + subversion/libsvn_ra_serf/property.c | 1263 ++ subversion/libsvn_ra_serf/ra_serf.h | 1785 +++ subversion/libsvn_ra_serf/replay.c | 920 ++ subversion/libsvn_ra_serf/sb_bucket.c | 185 + subversion/libsvn_ra_serf/serf.c | 1246 ++ subversion/libsvn_ra_serf/update.c | 3639 +++++ subversion/libsvn_ra_serf/util.c | 2614 ++++ subversion/libsvn_ra_serf/util_error.c | 100 + subversion/libsvn_ra_serf/xml.c | 825 + subversion/libsvn_ra_svn/client.c | 2739 ++++ subversion/libsvn_ra_svn/cram.c | 221 + subversion/libsvn_ra_svn/cyrus_auth.c | 954 ++ subversion/libsvn_ra_svn/deprecated.c | 234 + subversion/libsvn_ra_svn/editorp.c | 1044 ++ subversion/libsvn_ra_svn/internal_auth.c | 121 + subversion/libsvn_ra_svn/marshal.c | 2289 +++ subversion/libsvn_ra_svn/protocol | 625 + subversion/libsvn_ra_svn/ra_svn.h | 249 + subversion/libsvn_ra_svn/streams.c | 255 + subversion/libsvn_ra_svn/version.c | 33 + subversion/libsvn_repos/authz.c | 1075 ++ subversion/libsvn_repos/commit.c | 1381 ++ subversion/libsvn_repos/delta.c | 1074 ++ subversion/libsvn_repos/deprecated.c | 1017 ++ subversion/libsvn_repos/dump.c | 1503 ++ subversion/libsvn_repos/fs-wrap.c | 844 ++ subversion/libsvn_repos/hooks.c | 890 ++ subversion/libsvn_repos/load-fs-vtable.c | 1140 ++ subversion/libsvn_repos/load.c | 684 + subversion/libsvn_repos/log.c | 2369 +++ subversion/libsvn_repos/node_tree.c | 431 + subversion/libsvn_repos/notify.c | 44 + subversion/libsvn_repos/replay.c | 1591 ++ subversion/libsvn_repos/reporter.c | 1610 ++ subversion/libsvn_repos/repos.c | 2132 +++ subversion/libsvn_repos/repos.h | 425 + subversion/libsvn_repos/rev_hunt.c | 1699 +++ subversion/libsvn_subr/adler32.c | 101 + subversion/libsvn_subr/atomic.c | 85 + subversion/libsvn_subr/auth.c | 652 + subversion/libsvn_subr/auth.h | 49 + subversion/libsvn_subr/base64.c | 567 + subversion/libsvn_subr/cache-inprocess.c | 648 + subversion/libsvn_subr/cache-membuffer.c | 2369 +++ subversion/libsvn_subr/cache-memcache.c | 583 + subversion/libsvn_subr/cache.c | 265 + subversion/libsvn_subr/cache.h | 109 + subversion/libsvn_subr/cache_config.c | 169 + subversion/libsvn_subr/checksum.c | 500 + subversion/libsvn_subr/cmdline.c | 1312 ++ subversion/libsvn_subr/compat.c | 159 + subversion/libsvn_subr/config.c | 1208 ++ subversion/libsvn_subr/config_auth.c | 277 + subversion/libsvn_subr/config_file.c | 1260 ++ subversion/libsvn_subr/config_impl.h | 161 + subversion/libsvn_subr/config_win.c | 259 + subversion/libsvn_subr/crypto.c | 705 + subversion/libsvn_subr/crypto.h | 141 + subversion/libsvn_subr/ctype.c | 319 + subversion/libsvn_subr/date.c | 393 + subversion/libsvn_subr/debug.c | 155 + subversion/libsvn_subr/deprecated.c | 1304 ++ subversion/libsvn_subr/dirent_uri.c | 2597 ++++ subversion/libsvn_subr/dirent_uri.h | 40 + subversion/libsvn_subr/dso.c | 117 + subversion/libsvn_subr/eol.c | 108 + subversion/libsvn_subr/error.c | 800 + subversion/libsvn_subr/genctype.py | 114 + subversion/libsvn_subr/gpg_agent.c | 463 + subversion/libsvn_subr/hash.c | 642 + subversion/libsvn_subr/internal_statements.h | 76 + subversion/libsvn_subr/internal_statements.sql | 47 + subversion/libsvn_subr/io.c | 4768 ++++++ subversion/libsvn_subr/iter.c | 216 + subversion/libsvn_subr/lock.c | 60 + subversion/libsvn_subr/log.c | 396 + subversion/libsvn_subr/macos_keychain.c | 263 + subversion/libsvn_subr/magic.c | 161 + subversion/libsvn_subr/md5.c | 110 + subversion/libsvn_subr/md5.h | 71 + subversion/libsvn_subr/mergeinfo.c | 2631 ++++ subversion/libsvn_subr/mutex.c | 83 + subversion/libsvn_subr/named_atomic.c | 655 + subversion/libsvn_subr/nls.c | 132 + subversion/libsvn_subr/opt.c | 1240 ++ subversion/libsvn_subr/opt.h | 54 + subversion/libsvn_subr/path.c | 1315 ++ subversion/libsvn_subr/pool.c | 142 + subversion/libsvn_subr/prompt.c | 954 ++ subversion/libsvn_subr/properties.c | 507 + subversion/libsvn_subr/pseudo_md5.c | 422 + subversion/libsvn_subr/quoprint.c | 309 + subversion/libsvn_subr/sha1.c | 82 + subversion/libsvn_subr/sha1.h | 70 + subversion/libsvn_subr/simple_providers.c | 734 + subversion/libsvn_subr/skel.c | 881 ++ subversion/libsvn_subr/sorts.c | 309 + subversion/libsvn_subr/spillbuf.c | 615 + subversion/libsvn_subr/sqlite.c | 1294 ++ subversion/libsvn_subr/sqlite3wrapper.c | 62 + subversion/libsvn_subr/ssl_client_cert_providers.c | 209 + .../libsvn_subr/ssl_client_cert_pw_providers.c | 506 + .../libsvn_subr/ssl_server_trust_providers.c | 234 + subversion/libsvn_subr/stream.c | 1826 +++ subversion/libsvn_subr/string.c | 1273 ++ subversion/libsvn_subr/subst.c | 2025 +++ subversion/libsvn_subr/sysinfo.c | 1132 ++ subversion/libsvn_subr/sysinfo.h | 69 + subversion/libsvn_subr/target.c | 335 + subversion/libsvn_subr/temp_serializer.c | 382 + subversion/libsvn_subr/time.c | 277 + subversion/libsvn_subr/token.c | 98 + subversion/libsvn_subr/types.c | 340 + subversion/libsvn_subr/user.c | 86 + subversion/libsvn_subr/username_providers.c | 306 + subversion/libsvn_subr/utf.c | 1075 ++ subversion/libsvn_subr/utf_validate.c | 485 + subversion/libsvn_subr/utf_width.c | 283 + subversion/libsvn_subr/validate.c | 102 + subversion/libsvn_subr/version.c | 291 + subversion/libsvn_subr/win32_crashrpt.c | 805 + subversion/libsvn_subr/win32_crashrpt.h | 35 + subversion/libsvn_subr/win32_crashrpt_dll.h | 93 + subversion/libsvn_subr/win32_crypto.c | 492 + subversion/libsvn_subr/win32_xlate.c | 238 + subversion/libsvn_subr/win32_xlate.h | 52 + subversion/libsvn_subr/xml.c | 655 + subversion/libsvn_wc/README | 195 + subversion/libsvn_wc/adm_crawler.c | 1239 ++ subversion/libsvn_wc/adm_files.c | 584 + subversion/libsvn_wc/adm_files.h | 161 + subversion/libsvn_wc/adm_ops.c | 1400 ++ subversion/libsvn_wc/ambient_depth_filter_editor.c | 715 + subversion/libsvn_wc/cleanup.c | 231 + subversion/libsvn_wc/conflicts.c | 3141 ++++ subversion/libsvn_wc/conflicts.h | 443 + subversion/libsvn_wc/context.c | 116 + subversion/libsvn_wc/copy.c | 1048 ++ subversion/libsvn_wc/crop.c | 361 + subversion/libsvn_wc/delete.c | 508 + subversion/libsvn_wc/deprecated.c | 4582 ++++++ subversion/libsvn_wc/diff.h | 144 + subversion/libsvn_wc/diff_editor.c | 2747 ++++ subversion/libsvn_wc/diff_local.c | 541 + subversion/libsvn_wc/entries.c | 2738 ++++ subversion/libsvn_wc/entries.h | 164 + subversion/libsvn_wc/externals.c | 1686 +++ subversion/libsvn_wc/info.c | 580 + subversion/libsvn_wc/lock.c | 1656 ++ subversion/libsvn_wc/lock.h | 91 + subversion/libsvn_wc/merge.c | 1424 ++ subversion/libsvn_wc/node.c | 1418 ++ subversion/libsvn_wc/old-and-busted.c | 1340 ++ subversion/libsvn_wc/props.c | 2344 +++ subversion/libsvn_wc/props.h | 154 + subversion/libsvn_wc/questions.c | 621 + subversion/libsvn_wc/relocate.c | 170 + subversion/libsvn_wc/revert.c | 886 ++ subversion/libsvn_wc/revision_status.c | 67 + subversion/libsvn_wc/status.c | 3047 ++++ subversion/libsvn_wc/token-map.h | 70 + subversion/libsvn_wc/translate.c | 452 + subversion/libsvn_wc/translate.h | 189 + subversion/libsvn_wc/tree_conflicts.c | 513 + subversion/libsvn_wc/tree_conflicts.h | 93 + subversion/libsvn_wc/update_editor.c | 5486 +++++++ subversion/libsvn_wc/upgrade.c | 2376 +++ subversion/libsvn_wc/util.c | 636 + subversion/libsvn_wc/wc-checks.h | 55 + subversion/libsvn_wc/wc-checks.sql | 77 + subversion/libsvn_wc/wc-metadata.h | 516 + subversion/libsvn_wc/wc-metadata.sql | 951 ++ subversion/libsvn_wc/wc-queries.h | 3100 ++++ subversion/libsvn_wc/wc-queries.sql | 1693 +++ subversion/libsvn_wc/wc.h | 808 + subversion/libsvn_wc/wc_db.c | 15050 +++++++++++++++++++ subversion/libsvn_wc/wc_db.h | 3413 +++++ subversion/libsvn_wc/wc_db_pristine.c | 925 ++ subversion/libsvn_wc/wc_db_private.h | 458 + subversion/libsvn_wc/wc_db_update_move.c | 2631 ++++ subversion/libsvn_wc/wc_db_util.c | 228 + subversion/libsvn_wc/wc_db_wcroot.c | 900 ++ subversion/libsvn_wc/wcroot_anchor.c | 227 + subversion/libsvn_wc/workqueue.c | 1666 ++ subversion/libsvn_wc/workqueue.h | 235 + subversion/svn/add-cmd.c | 113 + subversion/svn/blame-cmd.c | 419 + subversion/svn/cat-cmd.c | 118 + subversion/svn/changelist-cmd.c | 149 + subversion/svn/checkout-cmd.c | 173 + subversion/svn/cl-conflicts.c | 454 + subversion/svn/cl-conflicts.h | 80 + subversion/svn/cl.h | 852 ++ subversion/svn/cleanup-cmd.c | 104 + subversion/svn/client_errors.h | 97 + subversion/svn/commit-cmd.c | 186 + subversion/svn/conflict-callbacks.c | 1369 ++ subversion/svn/copy-cmd.c | 184 + subversion/svn/delete-cmd.c | 95 + subversion/svn/deprecated.c | 41 + subversion/svn/diff-cmd.c | 476 + subversion/svn/export-cmd.c | 128 + subversion/svn/file-merge.c | 959 ++ subversion/svn/help-cmd.c | 153 + subversion/svn/import-cmd.c | 132 + subversion/svn/info-cmd.c | 683 + subversion/svn/list-cmd.c | 424 + subversion/svn/lock-cmd.c | 110 + subversion/svn/log-cmd.c | 875 ++ subversion/svn/merge-cmd.c | 467 + subversion/svn/mergeinfo-cmd.c | 349 + subversion/svn/mkdir-cmd.c | 104 + subversion/svn/move-cmd.c | 105 + subversion/svn/notify.c | 1222 ++ subversion/svn/patch-cmd.c | 98 + subversion/svn/propdel-cmd.c | 103 + subversion/svn/propedit-cmd.c | 356 + subversion/svn/propget-cmd.c | 493 + subversion/svn/proplist-cmd.c | 336 + subversion/svn/props.c | 356 + subversion/svn/propset-cmd.c | 191 + subversion/svn/relocate-cmd.c | 120 + subversion/svn/resolve-cmd.c | 131 + subversion/svn/resolved-cmd.c | 88 + subversion/svn/revert-cmd.c | 81 + subversion/svn/schema/blame.rnc | 42 + subversion/svn/schema/common.rnc | 77 + subversion/svn/schema/diff.rnc | 39 + subversion/svn/schema/info.rnc | 134 + subversion/svn/schema/list.rnc | 45 + subversion/svn/schema/log.rnc | 55 + subversion/svn/schema/props.rnc | 36 + subversion/svn/schema/status.rnc | 92 + subversion/svn/status-cmd.c | 416 + subversion/svn/status.c | 607 + subversion/svn/svn.1 | 47 + subversion/svn/svn.c | 2961 ++++ subversion/svn/switch-cmd.c | 199 + subversion/svn/unlock-cmd.c | 68 + subversion/svn/update-cmd.c | 196 + subversion/svn/upgrade-cmd.c | 78 + subversion/svn/util.c | 1109 ++ subversion/svn_private_config.h.in | 257 + subversion/svn_private_config.hw | 110 + subversion/svnadmin/svnadmin.1 | 47 + subversion/svnadmin/svnadmin.c | 2380 +++ subversion/svndumpfilter/svndumpfilter.1 | 47 + subversion/svndumpfilter/svndumpfilter.c | 1658 ++ subversion/svnlook/svnlook.1 | 47 + subversion/svnlook/svnlook.c | 2830 ++++ subversion/svnmucc/svnmucc.1 | 47 + subversion/svnmucc/svnmucc.c | 1460 ++ subversion/svnrdump/dump_editor.c | 1280 ++ subversion/svnrdump/load_editor.c | 1211 ++ subversion/svnrdump/svnrdump.1 | 47 + subversion/svnrdump/svnrdump.c | 1185 ++ subversion/svnrdump/svnrdump.h | 129 + subversion/svnrdump/util.c | 73 + subversion/svnserve/cyrus_auth.c | 377 + subversion/svnserve/log-escape.c | 143 + subversion/svnserve/serve.c | 3678 +++++ subversion/svnserve/server.h | 186 + subversion/svnserve/svnserve.8 | 138 + subversion/svnserve/svnserve.c | 1175 ++ subversion/svnserve/svnserve.conf.5 | 100 + subversion/svnserve/winservice.c | 490 + subversion/svnserve/winservice.h | 64 + subversion/svnsync/svnsync.1 | 47 + subversion/svnsync/svnsync.c | 2305 +++ subversion/svnsync/sync.c | 643 + subversion/svnsync/sync.h | 85 + subversion/svnversion/svnversion.1 | 47 + subversion/svnversion/svnversion.c | 300 + 551 files changed, 402644 insertions(+) create mode 100644 subversion/include/mod_authz_svn.h create mode 100644 subversion/include/mod_dav_svn.h create mode 100644 subversion/include/private/README create mode 100644 subversion/include/private/ra_svn_sasl.h create mode 100644 subversion/include/private/svn_adler32.h create mode 100644 subversion/include/private/svn_atomic.h create mode 100644 subversion/include/private/svn_auth_private.h create mode 100644 subversion/include/private/svn_cache.h create mode 100644 subversion/include/private/svn_client_private.h create mode 100644 subversion/include/private/svn_cmdline_private.h create mode 100644 subversion/include/private/svn_dav_protocol.h create mode 100644 subversion/include/private/svn_debug.h create mode 100644 subversion/include/private/svn_delta_private.h create mode 100644 subversion/include/private/svn_dep_compat.h create mode 100644 subversion/include/private/svn_diff_private.h create mode 100644 subversion/include/private/svn_diff_tree.h create mode 100644 subversion/include/private/svn_doxygen.h create mode 100644 subversion/include/private/svn_editor.h create mode 100644 subversion/include/private/svn_eol_private.h create mode 100644 subversion/include/private/svn_error_private.h create mode 100644 subversion/include/private/svn_fs_private.h create mode 100644 subversion/include/private/svn_fs_util.h create mode 100644 subversion/include/private/svn_fspath.h create mode 100644 subversion/include/private/svn_io_private.h create mode 100644 subversion/include/private/svn_log.h create mode 100644 subversion/include/private/svn_magic.h create mode 100644 subversion/include/private/svn_mergeinfo_private.h create mode 100644 subversion/include/private/svn_mutex.h create mode 100644 subversion/include/private/svn_named_atomic.h create mode 100644 subversion/include/private/svn_opt_private.h create mode 100644 subversion/include/private/svn_pseudo_md5.h create mode 100644 subversion/include/private/svn_ra_private.h create mode 100644 subversion/include/private/svn_ra_svn_private.h create mode 100644 subversion/include/private/svn_repos_private.h create mode 100644 subversion/include/private/svn_skel.h create mode 100644 subversion/include/private/svn_sqlite.h create mode 100644 subversion/include/private/svn_string_private.h create mode 100644 subversion/include/private/svn_subr_private.h create mode 100644 subversion/include/private/svn_temp_serializer.h create mode 100644 subversion/include/private/svn_token.h create mode 100644 subversion/include/private/svn_utf_private.h create mode 100644 subversion/include/private/svn_wc_private.h create mode 100644 subversion/include/svn_auth.h create mode 100644 subversion/include/svn_base64.h create mode 100644 subversion/include/svn_cache_config.h create mode 100644 subversion/include/svn_checksum.h create mode 100644 subversion/include/svn_client.h create mode 100644 subversion/include/svn_cmdline.h create mode 100644 subversion/include/svn_compat.h create mode 100644 subversion/include/svn_config.h create mode 100644 subversion/include/svn_ctype.h create mode 100644 subversion/include/svn_dav.h create mode 100644 subversion/include/svn_delta.h create mode 100644 subversion/include/svn_diff.h create mode 100644 subversion/include/svn_dirent_uri.h create mode 100644 subversion/include/svn_dso.h create mode 100644 subversion/include/svn_error.h create mode 100644 subversion/include/svn_error_codes.h create mode 100644 subversion/include/svn_fs.h create mode 100644 subversion/include/svn_hash.h create mode 100644 subversion/include/svn_io.h create mode 100644 subversion/include/svn_iter.h create mode 100644 subversion/include/svn_md5.h create mode 100644 subversion/include/svn_mergeinfo.h create mode 100644 subversion/include/svn_nls.h create mode 100644 subversion/include/svn_opt.h create mode 100644 subversion/include/svn_path.h create mode 100644 subversion/include/svn_pools.h create mode 100644 subversion/include/svn_props.h create mode 100644 subversion/include/svn_quoprint.h create mode 100644 subversion/include/svn_ra.h create mode 100644 subversion/include/svn_ra_svn.h create mode 100644 subversion/include/svn_repos.h create mode 100644 subversion/include/svn_sorts.h create mode 100644 subversion/include/svn_string.h create mode 100644 subversion/include/svn_subst.h create mode 100644 subversion/include/svn_time.h create mode 100644 subversion/include/svn_types.h create mode 100644 subversion/include/svn_user.h create mode 100644 subversion/include/svn_utf.h create mode 100644 subversion/include/svn_version.h create mode 100644 subversion/include/svn_wc.h create mode 100644 subversion/include/svn_xml.h create mode 100644 subversion/libsvn_auth_gnome_keyring/gnome_keyring.c create mode 100644 subversion/libsvn_auth_gnome_keyring/version.c create mode 100644 subversion/libsvn_auth_kwallet/kwallet.cpp create mode 100644 subversion/libsvn_auth_kwallet/version.c create mode 100644 subversion/libsvn_client/add.c create mode 100644 subversion/libsvn_client/blame.c create mode 100644 subversion/libsvn_client/cat.c create mode 100644 subversion/libsvn_client/changelist.c create mode 100644 subversion/libsvn_client/checkout.c create mode 100644 subversion/libsvn_client/cleanup.c create mode 100644 subversion/libsvn_client/client.h create mode 100644 subversion/libsvn_client/cmdline.c create mode 100644 subversion/libsvn_client/commit.c create mode 100644 subversion/libsvn_client/commit_util.c create mode 100644 subversion/libsvn_client/compat_providers.c create mode 100644 subversion/libsvn_client/copy.c create mode 100644 subversion/libsvn_client/copy_foreign.c create mode 100644 subversion/libsvn_client/ctx.c create mode 100644 subversion/libsvn_client/delete.c create mode 100644 subversion/libsvn_client/deprecated.c create mode 100644 subversion/libsvn_client/diff.c create mode 100644 subversion/libsvn_client/diff_local.c create mode 100644 subversion/libsvn_client/diff_summarize.c create mode 100644 subversion/libsvn_client/export.c create mode 100644 subversion/libsvn_client/externals.c create mode 100644 subversion/libsvn_client/import.c create mode 100644 subversion/libsvn_client/info.c create mode 100644 subversion/libsvn_client/iprops.c create mode 100644 subversion/libsvn_client/list.c create mode 100644 subversion/libsvn_client/locking_commands.c create mode 100644 subversion/libsvn_client/log.c create mode 100644 subversion/libsvn_client/merge.c create mode 100644 subversion/libsvn_client/mergeinfo.c create mode 100644 subversion/libsvn_client/mergeinfo.h create mode 100644 subversion/libsvn_client/patch.c create mode 100644 subversion/libsvn_client/prop_commands.c create mode 100644 subversion/libsvn_client/ra.c create mode 100644 subversion/libsvn_client/relocate.c create mode 100644 subversion/libsvn_client/repos_diff.c create mode 100644 subversion/libsvn_client/resolved.c create mode 100644 subversion/libsvn_client/revert.c create mode 100644 subversion/libsvn_client/revisions.c create mode 100644 subversion/libsvn_client/status.c create mode 100644 subversion/libsvn_client/switch.c create mode 100644 subversion/libsvn_client/update.c create mode 100644 subversion/libsvn_client/upgrade.c create mode 100644 subversion/libsvn_client/url.c create mode 100644 subversion/libsvn_client/util.c create mode 100644 subversion/libsvn_client/version.c create mode 100644 subversion/libsvn_delta/cancel.c create mode 100644 subversion/libsvn_delta/compat.c create mode 100644 subversion/libsvn_delta/compose_delta.c create mode 100644 subversion/libsvn_delta/debug_editor.c create mode 100644 subversion/libsvn_delta/debug_editor.h create mode 100644 subversion/libsvn_delta/default_editor.c create mode 100644 subversion/libsvn_delta/delta.h create mode 100644 subversion/libsvn_delta/deprecated.c create mode 100644 subversion/libsvn_delta/depth_filter_editor.c create mode 100644 subversion/libsvn_delta/editor.c create mode 100644 subversion/libsvn_delta/path_driver.c create mode 100644 subversion/libsvn_delta/svndiff.c create mode 100644 subversion/libsvn_delta/text_delta.c create mode 100644 subversion/libsvn_delta/version.c create mode 100644 subversion/libsvn_delta/xdelta.c create mode 100644 subversion/libsvn_diff/deprecated.c create mode 100644 subversion/libsvn_diff/diff.c create mode 100644 subversion/libsvn_diff/diff.h create mode 100644 subversion/libsvn_diff/diff3.c create mode 100644 subversion/libsvn_diff/diff4.c create mode 100644 subversion/libsvn_diff/diff_file.c create mode 100644 subversion/libsvn_diff/diff_memory.c create mode 100644 subversion/libsvn_diff/diff_tree.c create mode 100644 subversion/libsvn_diff/lcs.c create mode 100644 subversion/libsvn_diff/parse-diff.c create mode 100644 subversion/libsvn_diff/token.c create mode 100644 subversion/libsvn_diff/util.c create mode 100644 subversion/libsvn_fs/access.c create mode 100644 subversion/libsvn_fs/editor.c create mode 100644 subversion/libsvn_fs/fs-loader.c create mode 100644 subversion/libsvn_fs/fs-loader.h create mode 100644 subversion/libsvn_fs_base/bdb/bdb-err.c create mode 100644 subversion/libsvn_fs_base/bdb/bdb-err.h create mode 100644 subversion/libsvn_fs_base/bdb/bdb_compat.c create mode 100644 subversion/libsvn_fs_base/bdb/bdb_compat.h create mode 100644 subversion/libsvn_fs_base/bdb/changes-table.c create mode 100644 subversion/libsvn_fs_base/bdb/changes-table.h create mode 100644 subversion/libsvn_fs_base/bdb/checksum-reps-table.c create mode 100644 subversion/libsvn_fs_base/bdb/checksum-reps-table.h create mode 100644 subversion/libsvn_fs_base/bdb/copies-table.c create mode 100644 subversion/libsvn_fs_base/bdb/copies-table.h create mode 100644 subversion/libsvn_fs_base/bdb/dbt.c create mode 100644 subversion/libsvn_fs_base/bdb/dbt.h create mode 100644 subversion/libsvn_fs_base/bdb/env.c create mode 100644 subversion/libsvn_fs_base/bdb/env.h create mode 100644 subversion/libsvn_fs_base/bdb/lock-tokens-table.c create mode 100644 subversion/libsvn_fs_base/bdb/lock-tokens-table.h create mode 100644 subversion/libsvn_fs_base/bdb/locks-table.c create mode 100644 subversion/libsvn_fs_base/bdb/locks-table.h create mode 100644 subversion/libsvn_fs_base/bdb/miscellaneous-table.c create mode 100644 subversion/libsvn_fs_base/bdb/miscellaneous-table.h create mode 100644 subversion/libsvn_fs_base/bdb/node-origins-table.c create mode 100644 subversion/libsvn_fs_base/bdb/node-origins-table.h create mode 100644 subversion/libsvn_fs_base/bdb/nodes-table.c create mode 100644 subversion/libsvn_fs_base/bdb/nodes-table.h create mode 100644 subversion/libsvn_fs_base/bdb/reps-table.c create mode 100644 subversion/libsvn_fs_base/bdb/reps-table.h create mode 100644 subversion/libsvn_fs_base/bdb/rev-table.c create mode 100644 subversion/libsvn_fs_base/bdb/rev-table.h create mode 100644 subversion/libsvn_fs_base/bdb/strings-table.c create mode 100644 subversion/libsvn_fs_base/bdb/strings-table.h create mode 100644 subversion/libsvn_fs_base/bdb/txn-table.c create mode 100644 subversion/libsvn_fs_base/bdb/txn-table.h create mode 100644 subversion/libsvn_fs_base/bdb/uuids-table.c create mode 100644 subversion/libsvn_fs_base/bdb/uuids-table.h create mode 100644 subversion/libsvn_fs_base/dag.c create mode 100644 subversion/libsvn_fs_base/dag.h create mode 100644 subversion/libsvn_fs_base/err.c create mode 100644 subversion/libsvn_fs_base/err.h create mode 100644 subversion/libsvn_fs_base/fs.c create mode 100644 subversion/libsvn_fs_base/fs.h create mode 100644 subversion/libsvn_fs_base/id.c create mode 100644 subversion/libsvn_fs_base/id.h create mode 100644 subversion/libsvn_fs_base/key-gen.c create mode 100644 subversion/libsvn_fs_base/key-gen.h create mode 100644 subversion/libsvn_fs_base/lock.c create mode 100644 subversion/libsvn_fs_base/lock.h create mode 100644 subversion/libsvn_fs_base/node-rev.c create mode 100644 subversion/libsvn_fs_base/node-rev.h create mode 100644 subversion/libsvn_fs_base/notes/TODO create mode 100644 subversion/libsvn_fs_base/notes/fs-history create mode 100644 subversion/libsvn_fs_base/notes/structure create mode 100644 subversion/libsvn_fs_base/reps-strings.c create mode 100644 subversion/libsvn_fs_base/reps-strings.h create mode 100644 subversion/libsvn_fs_base/revs-txns.c create mode 100644 subversion/libsvn_fs_base/revs-txns.h create mode 100644 subversion/libsvn_fs_base/trail.c create mode 100644 subversion/libsvn_fs_base/trail.h create mode 100644 subversion/libsvn_fs_base/tree.c create mode 100644 subversion/libsvn_fs_base/tree.h create mode 100644 subversion/libsvn_fs_base/util/fs_skels.c create mode 100644 subversion/libsvn_fs_base/util/fs_skels.h create mode 100644 subversion/libsvn_fs_base/uuid.c create mode 100644 subversion/libsvn_fs_base/uuid.h create mode 100644 subversion/libsvn_fs_fs/caching.c create mode 100644 subversion/libsvn_fs_fs/dag.c create mode 100644 subversion/libsvn_fs_fs/dag.h create mode 100644 subversion/libsvn_fs_fs/fs.c create mode 100644 subversion/libsvn_fs_fs/fs.h create mode 100644 subversion/libsvn_fs_fs/fs_fs.c create mode 100644 subversion/libsvn_fs_fs/fs_fs.h create mode 100644 subversion/libsvn_fs_fs/id.c create mode 100644 subversion/libsvn_fs_fs/id.h create mode 100644 subversion/libsvn_fs_fs/key-gen.c create mode 100644 subversion/libsvn_fs_fs/key-gen.h create mode 100644 subversion/libsvn_fs_fs/lock.c create mode 100644 subversion/libsvn_fs_fs/lock.h create mode 100644 subversion/libsvn_fs_fs/rep-cache-db.h create mode 100644 subversion/libsvn_fs_fs/rep-cache-db.sql create mode 100644 subversion/libsvn_fs_fs/rep-cache.c create mode 100644 subversion/libsvn_fs_fs/rep-cache.h create mode 100644 subversion/libsvn_fs_fs/structure create mode 100644 subversion/libsvn_fs_fs/temp_serializer.c create mode 100644 subversion/libsvn_fs_fs/temp_serializer.h create mode 100644 subversion/libsvn_fs_fs/tree.c create mode 100644 subversion/libsvn_fs_fs/tree.h create mode 100644 subversion/libsvn_fs_util/fs-util.c create mode 100644 subversion/libsvn_ra/compat.c create mode 100644 subversion/libsvn_ra/debug_reporter.c create mode 100644 subversion/libsvn_ra/debug_reporter.h create mode 100644 subversion/libsvn_ra/deprecated.c create mode 100644 subversion/libsvn_ra/deprecated.h create mode 100644 subversion/libsvn_ra/editor.c create mode 100644 subversion/libsvn_ra/ra_loader.c create mode 100644 subversion/libsvn_ra/ra_loader.h create mode 100644 subversion/libsvn_ra/util.c create mode 100644 subversion/libsvn_ra/wrapper_template.h create mode 100644 subversion/libsvn_ra_local/ra_local.h create mode 100644 subversion/libsvn_ra_local/ra_plugin.c create mode 100644 subversion/libsvn_ra_local/split_url.c create mode 100644 subversion/libsvn_ra_serf/README create mode 100644 subversion/libsvn_ra_serf/blame.c create mode 100644 subversion/libsvn_ra_serf/blncache.c create mode 100644 subversion/libsvn_ra_serf/blncache.h create mode 100644 subversion/libsvn_ra_serf/commit.c create mode 100644 subversion/libsvn_ra_serf/get_deleted_rev.c create mode 100644 subversion/libsvn_ra_serf/getdate.c create mode 100644 subversion/libsvn_ra_serf/getlocations.c create mode 100644 subversion/libsvn_ra_serf/getlocationsegments.c create mode 100644 subversion/libsvn_ra_serf/getlocks.c create mode 100644 subversion/libsvn_ra_serf/inherited_props.c create mode 100644 subversion/libsvn_ra_serf/locks.c create mode 100644 subversion/libsvn_ra_serf/log.c create mode 100644 subversion/libsvn_ra_serf/merge.c create mode 100644 subversion/libsvn_ra_serf/mergeinfo.c create mode 100644 subversion/libsvn_ra_serf/options.c create mode 100644 subversion/libsvn_ra_serf/property.c create mode 100644 subversion/libsvn_ra_serf/ra_serf.h create mode 100644 subversion/libsvn_ra_serf/replay.c create mode 100644 subversion/libsvn_ra_serf/sb_bucket.c create mode 100644 subversion/libsvn_ra_serf/serf.c create mode 100644 subversion/libsvn_ra_serf/update.c create mode 100644 subversion/libsvn_ra_serf/util.c create mode 100644 subversion/libsvn_ra_serf/util_error.c create mode 100644 subversion/libsvn_ra_serf/xml.c create mode 100644 subversion/libsvn_ra_svn/client.c create mode 100644 subversion/libsvn_ra_svn/cram.c create mode 100644 subversion/libsvn_ra_svn/cyrus_auth.c create mode 100644 subversion/libsvn_ra_svn/deprecated.c create mode 100644 subversion/libsvn_ra_svn/editorp.c create mode 100644 subversion/libsvn_ra_svn/internal_auth.c create mode 100644 subversion/libsvn_ra_svn/marshal.c create mode 100644 subversion/libsvn_ra_svn/protocol create mode 100644 subversion/libsvn_ra_svn/ra_svn.h create mode 100644 subversion/libsvn_ra_svn/streams.c create mode 100644 subversion/libsvn_ra_svn/version.c create mode 100644 subversion/libsvn_repos/authz.c create mode 100644 subversion/libsvn_repos/commit.c create mode 100644 subversion/libsvn_repos/delta.c create mode 100644 subversion/libsvn_repos/deprecated.c create mode 100644 subversion/libsvn_repos/dump.c create mode 100644 subversion/libsvn_repos/fs-wrap.c create mode 100644 subversion/libsvn_repos/hooks.c create mode 100644 subversion/libsvn_repos/load-fs-vtable.c create mode 100644 subversion/libsvn_repos/load.c create mode 100644 subversion/libsvn_repos/log.c create mode 100644 subversion/libsvn_repos/node_tree.c create mode 100644 subversion/libsvn_repos/notify.c create mode 100644 subversion/libsvn_repos/replay.c create mode 100644 subversion/libsvn_repos/reporter.c create mode 100644 subversion/libsvn_repos/repos.c create mode 100644 subversion/libsvn_repos/repos.h create mode 100644 subversion/libsvn_repos/rev_hunt.c create mode 100644 subversion/libsvn_subr/adler32.c create mode 100644 subversion/libsvn_subr/atomic.c create mode 100644 subversion/libsvn_subr/auth.c create mode 100644 subversion/libsvn_subr/auth.h create mode 100644 subversion/libsvn_subr/base64.c create mode 100644 subversion/libsvn_subr/cache-inprocess.c create mode 100644 subversion/libsvn_subr/cache-membuffer.c create mode 100644 subversion/libsvn_subr/cache-memcache.c create mode 100644 subversion/libsvn_subr/cache.c create mode 100644 subversion/libsvn_subr/cache.h create mode 100644 subversion/libsvn_subr/cache_config.c create mode 100644 subversion/libsvn_subr/checksum.c create mode 100644 subversion/libsvn_subr/cmdline.c create mode 100644 subversion/libsvn_subr/compat.c create mode 100644 subversion/libsvn_subr/config.c create mode 100644 subversion/libsvn_subr/config_auth.c create mode 100644 subversion/libsvn_subr/config_file.c create mode 100644 subversion/libsvn_subr/config_impl.h create mode 100644 subversion/libsvn_subr/config_win.c create mode 100644 subversion/libsvn_subr/crypto.c create mode 100644 subversion/libsvn_subr/crypto.h create mode 100644 subversion/libsvn_subr/ctype.c create mode 100644 subversion/libsvn_subr/date.c create mode 100644 subversion/libsvn_subr/debug.c create mode 100644 subversion/libsvn_subr/deprecated.c create mode 100644 subversion/libsvn_subr/dirent_uri.c create mode 100644 subversion/libsvn_subr/dirent_uri.h create mode 100644 subversion/libsvn_subr/dso.c create mode 100644 subversion/libsvn_subr/eol.c create mode 100644 subversion/libsvn_subr/error.c create mode 100755 subversion/libsvn_subr/genctype.py create mode 100644 subversion/libsvn_subr/gpg_agent.c create mode 100644 subversion/libsvn_subr/hash.c create mode 100644 subversion/libsvn_subr/internal_statements.h create mode 100644 subversion/libsvn_subr/internal_statements.sql create mode 100644 subversion/libsvn_subr/io.c create mode 100644 subversion/libsvn_subr/iter.c create mode 100644 subversion/libsvn_subr/lock.c create mode 100644 subversion/libsvn_subr/log.c create mode 100644 subversion/libsvn_subr/macos_keychain.c create mode 100644 subversion/libsvn_subr/magic.c create mode 100644 subversion/libsvn_subr/md5.c create mode 100644 subversion/libsvn_subr/md5.h create mode 100644 subversion/libsvn_subr/mergeinfo.c create mode 100644 subversion/libsvn_subr/mutex.c create mode 100644 subversion/libsvn_subr/named_atomic.c create mode 100644 subversion/libsvn_subr/nls.c create mode 100644 subversion/libsvn_subr/opt.c create mode 100644 subversion/libsvn_subr/opt.h create mode 100644 subversion/libsvn_subr/path.c create mode 100644 subversion/libsvn_subr/pool.c create mode 100644 subversion/libsvn_subr/prompt.c create mode 100644 subversion/libsvn_subr/properties.c create mode 100644 subversion/libsvn_subr/pseudo_md5.c create mode 100644 subversion/libsvn_subr/quoprint.c create mode 100644 subversion/libsvn_subr/sha1.c create mode 100644 subversion/libsvn_subr/sha1.h create mode 100644 subversion/libsvn_subr/simple_providers.c create mode 100644 subversion/libsvn_subr/skel.c create mode 100644 subversion/libsvn_subr/sorts.c create mode 100644 subversion/libsvn_subr/spillbuf.c create mode 100644 subversion/libsvn_subr/sqlite.c create mode 100644 subversion/libsvn_subr/sqlite3wrapper.c create mode 100644 subversion/libsvn_subr/ssl_client_cert_providers.c create mode 100644 subversion/libsvn_subr/ssl_client_cert_pw_providers.c create mode 100644 subversion/libsvn_subr/ssl_server_trust_providers.c create mode 100644 subversion/libsvn_subr/stream.c create mode 100644 subversion/libsvn_subr/string.c create mode 100644 subversion/libsvn_subr/subst.c create mode 100644 subversion/libsvn_subr/sysinfo.c create mode 100644 subversion/libsvn_subr/sysinfo.h create mode 100644 subversion/libsvn_subr/target.c create mode 100644 subversion/libsvn_subr/temp_serializer.c create mode 100644 subversion/libsvn_subr/time.c create mode 100644 subversion/libsvn_subr/token.c create mode 100644 subversion/libsvn_subr/types.c create mode 100644 subversion/libsvn_subr/user.c create mode 100644 subversion/libsvn_subr/username_providers.c create mode 100644 subversion/libsvn_subr/utf.c create mode 100644 subversion/libsvn_subr/utf_validate.c create mode 100644 subversion/libsvn_subr/utf_width.c create mode 100644 subversion/libsvn_subr/validate.c create mode 100644 subversion/libsvn_subr/version.c create mode 100644 subversion/libsvn_subr/win32_crashrpt.c create mode 100644 subversion/libsvn_subr/win32_crashrpt.h create mode 100644 subversion/libsvn_subr/win32_crashrpt_dll.h create mode 100644 subversion/libsvn_subr/win32_crypto.c create mode 100644 subversion/libsvn_subr/win32_xlate.c create mode 100644 subversion/libsvn_subr/win32_xlate.h create mode 100644 subversion/libsvn_subr/xml.c create mode 100644 subversion/libsvn_wc/README create mode 100644 subversion/libsvn_wc/adm_crawler.c create mode 100644 subversion/libsvn_wc/adm_files.c create mode 100644 subversion/libsvn_wc/adm_files.h create mode 100644 subversion/libsvn_wc/adm_ops.c create mode 100644 subversion/libsvn_wc/ambient_depth_filter_editor.c create mode 100644 subversion/libsvn_wc/cleanup.c create mode 100644 subversion/libsvn_wc/conflicts.c create mode 100644 subversion/libsvn_wc/conflicts.h create mode 100644 subversion/libsvn_wc/context.c create mode 100644 subversion/libsvn_wc/copy.c create mode 100644 subversion/libsvn_wc/crop.c create mode 100644 subversion/libsvn_wc/delete.c create mode 100644 subversion/libsvn_wc/deprecated.c create mode 100644 subversion/libsvn_wc/diff.h create mode 100644 subversion/libsvn_wc/diff_editor.c create mode 100644 subversion/libsvn_wc/diff_local.c create mode 100644 subversion/libsvn_wc/entries.c create mode 100644 subversion/libsvn_wc/entries.h create mode 100644 subversion/libsvn_wc/externals.c create mode 100644 subversion/libsvn_wc/info.c create mode 100644 subversion/libsvn_wc/lock.c create mode 100644 subversion/libsvn_wc/lock.h create mode 100644 subversion/libsvn_wc/merge.c create mode 100644 subversion/libsvn_wc/node.c create mode 100644 subversion/libsvn_wc/old-and-busted.c create mode 100644 subversion/libsvn_wc/props.c create mode 100644 subversion/libsvn_wc/props.h create mode 100644 subversion/libsvn_wc/questions.c create mode 100644 subversion/libsvn_wc/relocate.c create mode 100644 subversion/libsvn_wc/revert.c create mode 100644 subversion/libsvn_wc/revision_status.c create mode 100644 subversion/libsvn_wc/status.c create mode 100644 subversion/libsvn_wc/token-map.h create mode 100644 subversion/libsvn_wc/translate.c create mode 100644 subversion/libsvn_wc/translate.h create mode 100644 subversion/libsvn_wc/tree_conflicts.c create mode 100644 subversion/libsvn_wc/tree_conflicts.h create mode 100644 subversion/libsvn_wc/update_editor.c create mode 100644 subversion/libsvn_wc/upgrade.c create mode 100644 subversion/libsvn_wc/util.c create mode 100644 subversion/libsvn_wc/wc-checks.h create mode 100644 subversion/libsvn_wc/wc-checks.sql create mode 100644 subversion/libsvn_wc/wc-metadata.h create mode 100644 subversion/libsvn_wc/wc-metadata.sql create mode 100644 subversion/libsvn_wc/wc-queries.h create mode 100644 subversion/libsvn_wc/wc-queries.sql create mode 100644 subversion/libsvn_wc/wc.h create mode 100644 subversion/libsvn_wc/wc_db.c create mode 100644 subversion/libsvn_wc/wc_db.h create mode 100644 subversion/libsvn_wc/wc_db_pristine.c create mode 100644 subversion/libsvn_wc/wc_db_private.h create mode 100644 subversion/libsvn_wc/wc_db_update_move.c create mode 100644 subversion/libsvn_wc/wc_db_util.c create mode 100644 subversion/libsvn_wc/wc_db_wcroot.c create mode 100644 subversion/libsvn_wc/wcroot_anchor.c create mode 100644 subversion/libsvn_wc/workqueue.c create mode 100644 subversion/libsvn_wc/workqueue.h create mode 100644 subversion/svn/add-cmd.c create mode 100644 subversion/svn/blame-cmd.c create mode 100644 subversion/svn/cat-cmd.c create mode 100644 subversion/svn/changelist-cmd.c create mode 100644 subversion/svn/checkout-cmd.c create mode 100644 subversion/svn/cl-conflicts.c create mode 100644 subversion/svn/cl-conflicts.h create mode 100644 subversion/svn/cl.h create mode 100644 subversion/svn/cleanup-cmd.c create mode 100644 subversion/svn/client_errors.h create mode 100644 subversion/svn/commit-cmd.c create mode 100644 subversion/svn/conflict-callbacks.c create mode 100644 subversion/svn/copy-cmd.c create mode 100644 subversion/svn/delete-cmd.c create mode 100644 subversion/svn/deprecated.c create mode 100644 subversion/svn/diff-cmd.c create mode 100644 subversion/svn/export-cmd.c create mode 100644 subversion/svn/file-merge.c create mode 100644 subversion/svn/help-cmd.c create mode 100644 subversion/svn/import-cmd.c create mode 100644 subversion/svn/info-cmd.c create mode 100644 subversion/svn/list-cmd.c create mode 100644 subversion/svn/lock-cmd.c create mode 100644 subversion/svn/log-cmd.c create mode 100644 subversion/svn/merge-cmd.c create mode 100644 subversion/svn/mergeinfo-cmd.c create mode 100644 subversion/svn/mkdir-cmd.c create mode 100644 subversion/svn/move-cmd.c create mode 100644 subversion/svn/notify.c create mode 100644 subversion/svn/patch-cmd.c create mode 100644 subversion/svn/propdel-cmd.c create mode 100644 subversion/svn/propedit-cmd.c create mode 100644 subversion/svn/propget-cmd.c create mode 100644 subversion/svn/proplist-cmd.c create mode 100644 subversion/svn/props.c create mode 100644 subversion/svn/propset-cmd.c create mode 100644 subversion/svn/relocate-cmd.c create mode 100644 subversion/svn/resolve-cmd.c create mode 100644 subversion/svn/resolved-cmd.c create mode 100644 subversion/svn/revert-cmd.c create mode 100644 subversion/svn/schema/blame.rnc create mode 100644 subversion/svn/schema/common.rnc create mode 100644 subversion/svn/schema/diff.rnc create mode 100644 subversion/svn/schema/info.rnc create mode 100644 subversion/svn/schema/list.rnc create mode 100644 subversion/svn/schema/log.rnc create mode 100644 subversion/svn/schema/props.rnc create mode 100644 subversion/svn/schema/status.rnc create mode 100644 subversion/svn/status-cmd.c create mode 100644 subversion/svn/status.c create mode 100644 subversion/svn/svn.1 create mode 100644 subversion/svn/svn.c create mode 100644 subversion/svn/switch-cmd.c create mode 100644 subversion/svn/unlock-cmd.c create mode 100644 subversion/svn/update-cmd.c create mode 100644 subversion/svn/upgrade-cmd.c create mode 100644 subversion/svn/util.c create mode 100644 subversion/svn_private_config.h.in create mode 100644 subversion/svn_private_config.hw create mode 100644 subversion/svnadmin/svnadmin.1 create mode 100644 subversion/svnadmin/svnadmin.c create mode 100644 subversion/svndumpfilter/svndumpfilter.1 create mode 100644 subversion/svndumpfilter/svndumpfilter.c create mode 100644 subversion/svnlook/svnlook.1 create mode 100644 subversion/svnlook/svnlook.c create mode 100644 subversion/svnmucc/svnmucc.1 create mode 100644 subversion/svnmucc/svnmucc.c create mode 100644 subversion/svnrdump/dump_editor.c create mode 100644 subversion/svnrdump/load_editor.c create mode 100644 subversion/svnrdump/svnrdump.1 create mode 100644 subversion/svnrdump/svnrdump.c create mode 100644 subversion/svnrdump/svnrdump.h create mode 100644 subversion/svnrdump/util.c create mode 100644 subversion/svnserve/cyrus_auth.c create mode 100644 subversion/svnserve/log-escape.c create mode 100644 subversion/svnserve/serve.c create mode 100644 subversion/svnserve/server.h create mode 100644 subversion/svnserve/svnserve.8 create mode 100644 subversion/svnserve/svnserve.c create mode 100644 subversion/svnserve/svnserve.conf.5 create mode 100644 subversion/svnserve/winservice.c create mode 100644 subversion/svnserve/winservice.h create mode 100644 subversion/svnsync/svnsync.1 create mode 100644 subversion/svnsync/svnsync.c create mode 100644 subversion/svnsync/sync.c create mode 100644 subversion/svnsync/sync.h create mode 100644 subversion/svnversion/svnversion.1 create mode 100644 subversion/svnversion/svnversion.c (limited to 'subversion') 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 + +#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 +#include + + +#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 +#else +#include +#endif + +#include +#include + +#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 + +#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 +#include + +#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 +#include + +#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 +#include + +#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 + +#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 +#include + +#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 const char * 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 +#include +#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: + * + *
+ *   SVN_DBG(("rev=%ld kind=%s\n", revnum, svn_node_kind_to_word(kind)));
+ * 
+ * + * outputs: + * + *
+ *   DBG: kitchensink.c: 42: rev=3141592 kind=file
+ * 
+ * + * 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 + +#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 + +#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 +#include + +#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 + +#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. + * + * + *

History

+ * + * 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. + * + * + *

Implementation Plan

+ * @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 + * significant 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 + *

Life-Cycle

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

Driving Order Restrictions

+ * 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 + * + *

Receiving Restrictions

+ * + * 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 + * + *

Timing and State

+ * 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. + * + *

Paths

+ * 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 + * + *

Pool Usage

+ * 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 + * + *

Cancellation

+ * 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 +#include + +#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 + - 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 const char * tokens, + * and with const char * 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 + +#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 +#include + +#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 + +#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 +#include +#include + +#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 + +#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 + +#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 +#include +#include + +#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 /* 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 + +#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 + +#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 + +#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 + +#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 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 +#include + +#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 const char * names onto const + * svn_string_t * 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 const char * URL + * to const char * 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 const char * + * local * absolute paths to const char * 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 const char * 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 +#include +#include +#include + +#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___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 svn_auth_provider_object_t * + * 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 + +#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 +#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 /* for apr_size_t */ +#include /* 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 +#include +#include +#include +#include +#include +#include + +#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 + * + * Peg and Operative Revisions 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 const char * property names to + * svn_string_t * 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 const char * 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.
+ * 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):
+ * #SVN_ERR_UNSUPPORTED_FEATURE if @a URL refers to a file rather + * than a directory;
+ * #SVN_ERR_RA_ILLEGAL_URL if @a URL does not exist;
+ * #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.
+ * If no error occurred, return #SVN_NO_ERROR. + * + * @since New in 1.5. + * + * @see #svn_depth_t
#svn_client_ctx_t
@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.
+ * 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):
+ * #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.
+ * If no error occurred, return #SVN_NO_ERROR. + * + * @since New in 1.7. + * + * @see #svn_depth_t
#svn_client_ctx_t
@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 (const char * names mapped to + * svn_string_t * 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 (const char * names mapped to + * svn_string_t * 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 (const char * names mapped to + * svn_string_t * 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 const char * 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 (const char * names mapped to + * svn_string_t * 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 const char * 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 const char * 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 const char *, 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 const char *) 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 const char * 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 const char *), 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 svn_opt_revision_range_t + * * 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 const char * merge source + * URLs to svn_rangelist_t * 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 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 @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 svn_client_copy_source_t * 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 (const char * names mapped to + * svn_string_t * 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 (const char * names mapped to + * svn_string_t * 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 (const char * names mapped to + * svn_string_t * 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 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 @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 svn_string_t * 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 const char * 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 `char *' 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 const char * 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 (const char *) 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 (const char *) to + * #svn_dirent_t *'s. + * + * If @a locks is not @c NULL, set @a *locks to a hash table mapping + * entry names (const char *) 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):
+ * 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.
+ * If no error occurred, return #SVN_NO_ERROR. + * + * @since New in 1.2. + * + * @see #svn_client_ctx_t
@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 const char * 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 const char * 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 const char * 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 + * const char * 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 + * const char * 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 const char * 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 +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#define APR_WANT_STDIO +#endif +#include + +#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 +#include +#include + +#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 /* for apr_int64_t */ +#include /* for apr_pool_t */ +#include /* 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 const char * keys and + * svn_string_t * 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 const char * keys and + * svn_string_t * 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 + + +#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 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 +#include +#include +#include +#include /* 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@' 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@' 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. + * + *

Function Usage

+ * + * 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. + * + * + *

Function Call Ordering

+ * + * 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. + * + * + *

Pool Usage

+ * + * 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. + * + * + *

Errors

+ * + * 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 +#include +#include /* 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 const char * 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 + 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 + int main(int argc, char *argv[]) { + printf("Hello World!\n"); + } @endverbatim + * + * And the modified text is: + * + * @verbatim + #include + 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 +#include +#include + +#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: + * -
"/foo/bar/baz"  ==>  "/foo/bar" and "baz"
+ * -
"/bar"          ==>  "/"  and "bar"
+ * -
"/"             ==>  "/"  and ""
+ * -
"bar"           ==>  ""   and "bar"
+ * -
""              ==>  ""   and ""
+ * Windows: -
"X:/"           ==>  "X:/" and ""
+ * -
"X:/foo"        ==>  "X:/" and "foo"
+ * -
"X:foo"         ==>  "X:" and "foo"
+ * Posix: -
"X:foo"         ==>  ""   and "X:foo"
+ * + * 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: + * -
"foo/bar/baz"  ==>  "foo/bar" and "baz"
+ * -
"bar"          ==>  ""  and "bar"
+ * -
""              ==>  ""   and ""
+ * + * 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: + * -
"http://server/foo/bar"  ==>  "http://server/foo" and "bar"
+ * + * 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 const char *'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 const char *'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 + +#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 /* for apr_size_t */ +#include /* APR's error system */ +#include /* for apr_pool_t */ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#define APR_WANT_STDIO +#endif +#include /* 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 do { ... } while (0) 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 +/* 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 +#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'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, + " 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 +#include +#include +#include +#include /* 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 /dev/tty is not acceptable default + * behavior for server processes, since those may both be equivalent to + * /dev/null. + */ +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 const char * 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 const char * 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 svn_prop_t + * elements. This is equivalent to calling svn_fs_change_txn_prop() + * multiple times with the @c name and @c value fields of each + * successive svn_prop_t, 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 + * const char * 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 char * 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 +#include +#include +#include +#include /* 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 " 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 const char * keys and svn_string_t * 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 const char * keys and svn_string_t * + * 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 const char * 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 +#include +#include +#include +#include +#include +#include +#include /* 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 mkdir -p. 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 (char *) 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 (char *) 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 const char * 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 const char* + * 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 const char * filename extensions to + * const char * 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 const char * + * filename extensions to const char * 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 /* for apr_ssize_t */ +#include /* for apr_pool_t */ +#include /* for apr_hash_t */ +#include /* 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 /* 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 +#include /* for apr_array_header_t */ +#include + +#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 +#include +#include +#include +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#define APR_WANT_STDIO +#endif +#include /* 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 const char * 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 + * const char * 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 +#include +#include + +#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: + * -
"/foo/bar/baz"  ==>  "/foo/bar" and "baz"
+ * -
"/bar"          ==>  "/"  and "bar"
+ * -
"/"             ==>  "/"  and "/"
+ * -
"X:/"           ==>  "X:/" and "X:/"
+ * -
"bar"           ==>  ""   and "bar"
+ * -
""              ==>  ""   and ""
+ * + * @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 const char *'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 const + * char * 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 const char * 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 + * svn_path_join(url, component, pool) 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 /* for apr_pool_t */ +#include /* for apr_array_header_t */ +#include /* 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 (const char *) inherited property names, and + * (svn_string_t *) property values. */ + apr_hash_t *prop_hash; + +} svn_prop_inherited_item_t; + + +/** + * Given a hash (keys const char * and values const + * svn_string_t) 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 const char * and + * values const svn_string_t *) 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 const svn_string_t. 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 ->nelts == 0. + */ +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 (const char *name -> const + * svn_string_t *value), 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: + * + *
+      name = svn:wc:dav_url
+      val  = http://www.example.com/repos/452348/e.289 
+ * + * 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 + +#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 +#include +#include +#include +#include +#include /* 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. + * + *
 ### 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...
+ */ + 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 const char * 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 + * (const char *) names to (@c svn_string_t *) 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 const char * 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 const char + * * paths (relative to the URL of @a session) to + * const char * 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 const char *, values are + * @c svn_string_t *. + * + * 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 (const char *), and the values dirents + * (@c svn_dirent_t *). Use @a pool for all allocations. + * + * @a dirent_fields controls which portions of the @c svn_dirent_t + * 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 const char *, values are + * @c svn_string_t *. + * + * @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 const char *, 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 + * const apr_hash_t * 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 const svn_log_changed_path2_t * 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 (@c svn_ra_plugin_t *). @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. + * + * + *
+ * 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.
+ * 
+ * + * @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 +#include +#include +#include +#include /* for apr_file_t */ +#include /* 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 +#include +#include +#include + +#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 const + * char * 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 decoded 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 + (once usernames become revision properties!) + * fetch the last revision where 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 const char * 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 + * const char * 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 const char * 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 char * 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 svn_repos_node_t * + * 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 const + * char * header-name ==> const char * 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 /* for apr_ssize_t */ +#include /* for apr_pool_t */ +#include /* for apr_array_header_t */ +#include /* 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 const char * for its data, so it is + * most appropriate for constant data and for functions which expect constant, + * counted data. Functions should generally use const @c svn_string_t + * * as their parameter to indicate they are expecting a constant, + * counted string. + * + * @c svn_stringbuf_t uses a plain char * for its data, so it is + * most appropriate for modifiable data. + * + *

Invariants

+ * + * 1. Null termination: + * + * Both structures maintain a significant invariant: + * + * s->data[s->len] == '\\0' + * + * 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. + * + *

Memory allocation

+ * + * 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 /* for apr_size_t */ +#include /* for apr_pool_t */ +#include /* 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 +#include +#include + +#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 const char *. + * Hash values are of type svn_string_t *. + * + * 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 +#include + +#include "svn_error.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/** Convert @a when to a const char * 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 const char * 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 +#include /* for ULONG_MAX */ + +#include /* for apr_size_t, apr_int64_t, ... */ +#include /* for apr_status_t */ +#include /* for apr_pool_t */ +#include /* for apr_hash_t */ +#include /* for apr_array_push() */ +#include /* for apr_time_t */ +#include /* 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" + 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." + 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 + +#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 +#include /* 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 +#endif +#include + +#include "svn_types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Symbols that define the version number. */ + +/* Version numbers: .. + * + * 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_libname_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 +#include +#include +#include +#include +#include + +#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 const char * directory names onto + * const char * 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 const char * + * directory names (directories traversed by @a traversal_info) to + * const char * 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 (const char *) entry names and values are + * (svn_wc_entry_t *). 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 (const svn_wc_entry_t *) 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 const char* + * absolute repository paths to svn_lock_t 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 svn_prop_t * + * 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 const char * child + * names, to const svn_dirent_t * 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 char * names onto + * svn_string_t * 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 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 @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 const char * 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 const char * 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 (const char *name -> const svn_string_t *value) + * 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 + * const char * 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 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 @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 APR_WRITE | APR_CREATE | APR_EXCL 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 const char * 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 +#include +#include + +#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 { + /** */ + svn_xml_normal = 1, + + /** , no cosmetic newline */ + svn_xml_protect_pcdata, + + /** */ + 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 char * keys and + * char * 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 char *'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
+ *         \
+ * 
+ * + * 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 char * key and char * 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 + * (char * keys mapping to char * 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 char ** 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 +#include +#include +#include + +#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 +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 (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 (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 (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 (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 (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 (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 +#include +#include +#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 + +#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 + +/* 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 + +#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(¤t_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 +#include +#include +#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(¤t_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, + ©_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 + +#include +#include +#include + +#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 +#include /* 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(¬_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, ©_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 +#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, ©from_rev, + ©from_repos_relpath, + ©from_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(×tamp_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(×tamp_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(×tamp_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(×tamp_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 +#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 + +#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 +#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 +#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 +#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 = ¬ify_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 *) = ©_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 +#include +#include +#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, ©from_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(©_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 +#include +#include +#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 +#include +#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 +#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 +#include +#include +#include + +#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 + +#include +#include + +#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 + +/*** 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, ©from_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 +#include +#include +#include +#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, ®ular_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, ®ular_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 *) = ⦥ + } + + 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 +#include + +#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(©from_path, ©from_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 +#include +#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 + +#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, ¤t, 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, + ©_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, + ©_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 + +#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 +#include +#include + +#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 + +#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 + +#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 +#include + +#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 + +#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 +#include + +#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 + +#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 + +#include /* 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 +#include + +#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 +#include + +#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 + +#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 +#include + +#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 +#include +#include "svn_delta.h" +#include "svn_io.h" +#include "delta.h" +#include "svn_pools.h" +#include "svn_private_config.h" +#include + +#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 +#include + +#include /* for APR_INLINE */ +#include /* 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 + +#include /* for APR_INLINE */ +#include + +#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 +#include +#include + +#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 +#include +#include + +#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 +#include +#include + +#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 +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +#include + +#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 +#include +#include + +#include + +#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 +#include +#include + +#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 MN. + * + * 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 +#include +#include + +#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 +#include +#include + +#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 +#include + +#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 + +#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 + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#include + +#include + +#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 + +#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 +#include + +#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 + +#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 + +#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(©_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_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(©, 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 +#include +#include +#include +#include + +#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 + +#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 + +#include +#if APR_HAS_THREADS +#include +#include +#endif + +#include +#include + +#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 +#include + +#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 +#include + +#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 +#include + +#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 +#include +#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 +#include + +#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(©key, *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, + ©key, &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 +#include + +#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 + +#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 + +#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(©_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(©_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 +#include + +#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 + +#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 +#include +#include + +#include +#include +#include + +#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 +#include +#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 +#include + +#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 +#include + +#define APR_WANT_STRFUNC +#include +#include +#include + +#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 + +#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 + +#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 + +#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: + + + +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 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 + +#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 + +#include +#include + +#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 +#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 +#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 +#include +#include +#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(©, 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, ©_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(©_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 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(©, 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, ©, 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(©, 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(©_id, ©, 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(©, 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(©_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(©_dst_rev, copy_dst_node, + trail, trail->pool)); + + /* Turn that revision into a revision root. */ + SVN_ERR(svn_fs_base__dag_revision_root(©_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(©_root, ©_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(©_src_rev, ©_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 + +#include +#include + +#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 +#include + +#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 + +#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(©_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 +#include +#include + +#include +#include +#include +#include + +#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 +#include +#include + +#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 + -, where runs from 0-99999 (see + create_txn_dir_pre_1_5() in fs_fs.c). For newer repositories, + the form is -<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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(¤t, ffd->revprop_generation)); + if (current == 0) + { + SVN_ERR(read_revprop_generation_file(¤t, 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(¤t, 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 = ¤t; + 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(¤t, + 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(¤t, + 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 "/-.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 . + */ + 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 +#include + +#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 +#include +#include +#include +#include +#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 + +#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 +#include +#include + +#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 directory, if sharding is in use (see below) + File containing rev + .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 directory, if sharding is in use (see below) + File containing rev-props for + .pack/ Pack directory, if the repo has been packed (see below) + . 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 + .txn/ Directory containing transaction + txn-protorevs/ Subdirectory containing transaction proto-revision files + .rev Proto-revision file for transaction + .rev-lock Write lock for proto-rev file + txn-current File containing the next transaction key + locks/ Subdirectory containing locks + / Subdirectory named for first 3 letters of an MD5 digest + File containing locks/children for path with + node-origins/ Lazy cache of origin noderevs for nodes + 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 + "\n" giving the youngest revision for the + repository. + + * Format 2 and below: a single line of the form " + \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 "\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 "