From 32547653cc5376642e1231fb644db99933ac8db4 Mon Sep 17 00:00:00 2001 From: Peter Wemm Date: Tue, 18 Jun 2013 02:07:41 +0000 Subject: [PATCH] Import trimmed svn-1.8.0-rc3 --- BUGS | 2 + CHANGES | 4623 +++ COMMITTERS | 234 + INSTALL | 1466 + LICENSE | 270 + Makefile.in | 907 + NOTICE | 23 + README | 84 + aclocal.m4 | 55 + autogen.sh | 210 + build-outputs.mk | 2894 ++ build.conf | 1377 + configure | 27324 ++++++++++++++++ configure.ac | 1521 + doc/README | 28 + doc/doxygen.conf | 1522 + doc/programmer/WritingChangeLogs.txt | 220 + doc/user/cvs-crossover-guide.html | 906 + doc/user/lj_article.txt | 323 + doc/user/svn-best-practices.html | 350 + gen-make.opts | 2 + gen-make.py | 326 + get-deps.sh | 173 + 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 + .../include/private/svn_client_private.h | 299 + .../include/private/svn_cmdline_private.h | 228 + subversion/include/private/svn_dav_protocol.h | 68 + subversion/include/private/svn_debug.h | 107 + .../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 + .../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 + .../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 + .../include/private/svn_ra_svn_private.h | 826 + .../include/private/svn_repos_private.h | 121 + subversion/include/private/svn_skel.h | 236 + subversion/include/private/svn_sqlite.h | 519 + .../include/private/svn_string_private.h | 222 + subversion/include/private/svn_subr_private.h | 340 + .../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 + .../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 + .../libsvn_fs_base/bdb/lock-tokens-table.c | 157 + .../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 + .../libsvn_fs_base/bdb/node-origins-table.c | 145 + .../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 + .../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 + .../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 + .../libsvn_subr/ssl_client_cert_providers.c | 209 + .../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 + .../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 + win-tests.py | 858 + 575 files changed, 448342 insertions(+) create mode 100644 BUGS create mode 100644 CHANGES create mode 100644 COMMITTERS create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 Makefile.in create mode 100644 NOTICE create mode 100644 README create mode 100644 aclocal.m4 create mode 100755 autogen.sh create mode 100644 build-outputs.mk create mode 100644 build.conf create mode 100755 configure create mode 100644 configure.ac create mode 100644 doc/README create mode 100644 doc/doxygen.conf create mode 100644 doc/programmer/WritingChangeLogs.txt create mode 100644 doc/user/cvs-crossover-guide.html create mode 100644 doc/user/lj_article.txt create mode 100644 doc/user/svn-best-practices.html create mode 100644 gen-make.opts create mode 100755 gen-make.py create mode 100755 get-deps.sh 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 create mode 100644 win-tests.py diff --git a/BUGS b/BUGS new file mode 100644 index 000000000000..6ce79ae9d643 --- /dev/null +++ b/BUGS @@ -0,0 +1,2 @@ +This document has been moved to +http://subversion.apache.org/docs/community-guide/issues.html diff --git a/CHANGES b/CHANGES new file mode 100644 index 000000000000..b0e61f2d67df --- /dev/null +++ b/CHANGES @@ -0,0 +1,4623 @@ +Version 1.8.0 +(18 Jun 2013, from /branches/1.8.x) +http://svn.apache.org/repos/asf/subversion/tags/1.8.0 + + User-visible changes: + - General: + * require serf as client-side http library (neon support removed) (r1349694) + * deprecate the Berkeley DB FS backend (libsvn_fs_base) (r1464985 et al) + + - Major new features: + * working copy records moves as first-class operation (issue #3631, #4232) + * merge uses reintegrate mode automatically when needed (r1369896 et al) + * FSFS: Packing of revision property shards (issue #3944) + * support inheritable properties (r1395109) + * repository can suggest config for autoprops and ignores (r1401908) + * support gpg-agent for password caching (r1151069) + * authz rules can be stored inside the repository (r1424780) + + - Minor new features and improvements (client-side): + * doubled svn:// protocol throughput (r1325899) + * optimize file/dir truename checks on Windows (r1435527) + * new 'commit --include-externals' option (related to issues #1167, #3563) + * new --include-externals option for 'svn list' (issue #4225) + * remove extraneous externals output from 'svn status -q' (issue #1935) + * reject some attempts to merge between unrelated branches (r1215273) + * new --ignore-properties option for 'svn diff' (r1239553, -617) + * new --properties-only option for 'svn diff' (r1336110) + * new --patch-compatible option for 'svn diff' (r1239561) + * new --no-diff-added option for 'svn diff' (r1433958) + * new w/c subtree duplication tool (tools/client-side/detach.py) + * new mergeinfo fixup tool (tools/client-side/mergeinfo-sanitizer.py) + * 'svn diff' can compare arbitrary files and directories (r1310291, et al) + * ra_serf avoids re-downloading content present in pristine store (r1333936) + * 'svn mergeinfo' now honors the --revision (-r) option (issue #4199) + * 'svn mergeinfo' now shows a summary graph by default (issue #4239) + * new --search and --search-and options for 'svn log' (r1354666, -83518) + * 'svn log' reports the node kind even for pre-1.6 revision files (r1242958) + * sort path list generated by "svn log -v --xml" (r1299323) + * new built-in interactive text conflict merge tool (r1357864, et al) + * 'svn --version' shows build system info (r1368662) + * 'svn --version --verbose' shows runtime environment info (r1370813 et al) + * 'svn' is now non-interactive when not run in a terminal device (r1424037) + * 'svn propset' checks spelling of reserved property names (r1470781) + * improve working copy performance on network disks (issue #4176) + * support for custom keyword definitions in svn:keywords (issue #890) + * svn:ignore __pycache__ directories by default (r1150073) + * 'svn diff --git' include copyfrom revision in "copied" headers (r1155279) + * svn:mergeinfo related operations now use much less memory (r1149519 et al) + * get list of supported schemes for RA libraries (r1148134) + * 'svn checkout' skips file externals from other repositories (r1153110) + * 'svn resolve' exits non-zero if conflicts could not be resolved (r1150439) + * let HTTPv2-aware clients fetch v2-style resources (r1161202) + * 'svn status' with better NLS support (r1157537, -682) + * better tracking of shallow-yet-complete merges (issues #4056, #4057) + * make 'svn status --quiet' w/ externals quieter still (issue #1935) + * ensure that conflict paths are shown relative-ized (r1337520) + * improve performance of local multi-target deletions (r1195873) + * various interactive conflict resolver improvements in 'svn' (r1440421 etc) + * improved tree diff implementation for diff and merge (r1440599 et al) + * tree conflicts on directories detected better during merges (issue #3150) + * allow reverting unmodified copies with 'svn remove' (r1442611) + * make 'svn diff' with mixed URL and local path targets work (r1442640) + * make 'svn patch' re-add deleted directories if needed (r1445333) + * make repos-wc diffs fully ancestry-aware (r1445904) + * 'svn diff --git' now implies 'svn diff --show-copies-as-adds' (r1446279) + * 'svn diff --show-copies-as-adds' now implies --notice-ancestry (r1446279) + * improved tree-conflict detection for 'svn switch' (r1449413, r1450582) + * allow up to 8 revision number digits in 'svn status -v' output (r1428637) + * show node kind (file or dir) in tree conflict descriptions (r1429907) + * restore deleted switched paths upon next update (issue #4295) + * add support for copying paths from foreign repositories (issue #3590) + * fix merge -cA,B with --accept option aborts if rA conflicts (issue #4238) + * 'svn resolve' interactive support; no longer requires --accept (r1336929) + * notify when removing externals leaves behind modified files (r1366021) + * new 'http-max-connections' configuration option for serf (r1421559) + * new 'http-bulk-updates' configuration option for serf (r1421490) + * 'svn cleanup' now runs SQLite "vacuum" to reclaim space (r1418459) + * 'svn info' displays repository-relative URL (r1415365) + * fix serf memory leak on checkout (issue #4194) + * detect duplicate paths setting svn:externals (issue #4227) + * make ra_serf work over HTTP/1.0 proxies (issue #3979) + * make ra_serf accept gzip compression for all responses (r1407454) + * double ra_serf performance for checkout and export (r1407545) + * improve network and disk i/o interleaving in ra_serf (r1407934) + * avoid assert in ra_serf when REPORT response was truncated (r1407935) + * rewrite ra_serf XML parser (r1409259 et al) + * ra_serf can create transaction with inline txnprops (r1375167) + * partially fix replace+propset of locked file fails over DAV (issue #3674) + * fix ra_serf doesn't handle bad baseline error from server (issue #4127) + * decreased default http timeout for ra_serf (issue #3968) + * prevent ra_serf from corrupting the working copy (issue #3993) + * ra_serf transmits property changes inline to reduce requests (r1378927) + * allow client to avoid SSL certificate prompts (issue #2410) + * improve interactive resolution of property conflicts (r1387678 et al) + * make ra_serf raise an error upon delta-base mismatch (issue #4235) + * tune ra_svn transmit buffer handling (r1391788) + * make 'svnrdump' work with serf (issue #4116) + * fix 'svnrdump' on path below repository root (issue #4101) + * support ipv6 in URLs (e.g. http://[::1]/svn/repos) (r1454047) + * conflict resolver now iterates paths in a sorted order (r1461820) + * mod_dav_svn does keyword expansion with 'kw=1' query arg (r1466055) + * add support for custom keyword definitions (issue #890) + + - Minor new features and improvements (server-side): + * improve performance of config file parsing (r1344347 et al) + * new 'svnadmin load --revision' load filtering support (issue #3734) + * new 'svnadmin hotcopy --incremental' support for FSFS (issue #3815) + * new 'svnadmin lock' / 'svnadmin unlock' subcommands (issue #3942, #4092) + * new SVNUseUTF8 configuration option for mod_dav_svn (issue #2487) + * new SVNHooksEnv configuration option for mod_dav_svn (r1239966) + * new SvnPubSub distributed commit hooks (tools/server-side/svnpubsub) + * new light-weight benchmarking client (tools/client-side/svn-bench) + * svndumpfilter dependency analysis (tools/server-side/svnpredumpfilter.py) + * new automatic working copy updater (tools/server-side/svnpubsub) + * new 'svnadmin freeze' subcommand (r1376228) + * 'svndumpfilter' now supports --delta dumpfiles (r1351009, -3745) + * new --drop-all-emtpy-revs option for 'svndumpfilter' (issue #3681) + * client version info now reported to commit hooks (issue #4124) + * txn name now reported to post-commit hooks (r1240856) + * support for server-side keyword expansion in mod_dav_svn (r1466055) + * FSFS now able to cache revision properties (r1326307) + * FSFS cache for changed-paths increases 'svn log' performance (r1378358) + * FSFS cache mergeinfo requested during 'log -g' (r1395439) + * many FSFS caching improvements (r1390435, r1390447) + * directory and property deltification option in FSFS (issue #4084) + * fine-grained control deltification behavior via fsfs.conf (r1311476) + * FSFS de-duplication ("rep sharing") now works within a revision (r1397773) + * FSFS de-duplication now works for properties as well (r1243312) + * read FSFS data using fewer fopen calls (issue #3372) + * 'svnadmin verify' will now check meta data (issues #3956, #4211) + * 'svnadmin verify' now checks for issue #4129 style corruption (r1304656) + * new --client-speed option for svnserve (r1391788) + * new --single-threaded option in svnserve (r1296018) + * hook script templates are now marked as executable (r1153414) + * error out on non-canonical fspaths in the authz file (r1166111) + * improve path lookup performance in FSFS (r1442088) + * svnserve now logs explicit path and reason for authz failures (r1446542) + * validate offsets from rep-cache to prevent FSFS corruption (issue #4277) + * new AuthzSVNGroupsFile option to store authz groups separately (r1438407) + * new 'SVNAllowBulkUpdates prefer' option for mod_dav_svn (r1417642, et al) + * new 'SVNMasterVersion' option for mod_dav_svn (r1398962) + * added virtual-host support to 'svnserve' (r1401296) + * new fsfs-stats tool which prints FSFS repository stats (r1410995) + * new fsfs-reorg tool to optimize FSFS packing (r1383214, r1385395) + * new --compatible-version option for 'svnadmin create' (r1407279 ) + * new --ignore-properties option for 'svnlook diff' (r1407905) + * new --properties-only option for 'svnlook diff' (r1407905) + * new --diff-cmd option for 'svnlook diff' (r1413449) + * allow leading "r"'s in http: ?p= and ?r= query parameters (r1221463) + * faster 'svn ls' for large directories (r1296627) + * mod_dav_svn now advertises supported POST types (r1375123) + * mod_dav_svn can create transaction with inline txnprops (r1375167) + * run start-commit hook after transaction creation (r1376201) + * avoid byte-for-byte comparison where it can be avoided (r1390641) + * various server-side performance improvements for 'log -g' (r1395442 et al) + * allow up to 10Gbit throughput with svnserve (r1391788) + * install mod_dontdothat correctly (r1454450) + * svnadmin verify can now verify transactions (r1462353) + * FSFS verifies revisions as they are added (r1462409) + + - Client-side bugfixes: + * fix inconsistent 'svn log' output for empty revisions (issue #3964) + * fix mis-ordered text output of 'svn log --diff' on Windows (r1220783) + * fix 'svn log --diff' on moved file (issue #4153). + * fix 'svn revert' of 'svn move' (issue #876) + * fix file externals wrongly "resurrecting" a deleted file (#4017) + * fix reporting of corrupted 1.6 w/cs by 'svn upgrade' (r1182904, -9) + * fix bug caused by URI-decoding local merge source paths (r1210539) + * fix properties out of sync with repos after merge and revert (issue #4305) + * fix merge of replacement on local delete fails (issue #4011) + * fix replacements on deletes produce wrong tree conflicts (issue #3806) + * made ra_serf handle location headers that are not RFC-compliant (r1443906) + * merge no longer errors out after resolving all conflicts (issue #4316) + * fix svn blame mis-categorizing file type as binary (issue #2089) + * fix externals not removed when working copy is made shallow (issue #3741) + * fix update under add with not-present parent (issue #4111) + * fix revert of files with svn:needs-lock under copied dirs (r1343168) + * fix repos->wc diff of local copied/moved-here directories (r1341927) + * fix repos->wc diff of local copied/moved-here files (r1341544) + * fix "svn diff -cN PATH" where PATH was deleted in rN (r1338708) + * fix dependency on APR hash order in several logic paths (r1338350 et al) + * fix path inconsistencies in 'svn diff' output (r1338291) + * fix misleading error message printed by 'svn switch' (issue #2337) + * fix bug in mergeinfo recording during foreign-repos merge (r1430310) + * fix spurious merge conflicts for binary files with keywords (issue #4221) + * fix patching symlinks with 'svn patch' (issue #4273) + * make 'svn switch' refresh lock information (issue #3376) + * fix 'svn diff' output doesn't apply as patch without fuzz (issue #3362) + * fix mergeinfo recording for multiple-revision-range merge (issue #4306) + * fix diffs shown by 'show-diff' conflict prompt option (r1438879) + * don't print an update summary header with no content (r1439480) + * make 'svn rm' remove externals registrations below its targets (r1361256) + * fix crashes in ra_serf where AVG 2012 Surf-Shield is in use (issue #4175) + * don't raise conflicts on identical binary files (issue #4128) + * improve error messages when wc.db missing (issue #4118) + * fix 'svn diff' showing wrong text change (issue #4270) + * fix 'svn diff -rN' failing to show local replace (issue #3797) + * fix 'svn diff' showing wrong revision (issue #4010) + * fix 'svn merge' showing spurious notifications (issue #2910) + * parse '.@HEAD' correctly (issue #3606) + * fix 'svn revert' after conflict in sparse working copy (issue #4168) + * fix bug in global/per-server config handling in serf (r1421516) + * properly display errors from serf (r1398742) + * fix crash in ra_serf (r1408291) + * fixed svnmucc propset and propdel on repository root (issue #3663) + * fix 'svn info' output with ancient svnserve servers (pre-1.2) (r1409732) + * ra_serf shows error message for 408 Request Timeout response (r1410983) + * fix handling of "\ No newline ..." in diff/patch (r1411723, r1412382) + * allow infinite http timeout in ra_serf (r1411976) + * using unknown svn: property names now requires --force (issue #4261) + * fix handling of case insensitive configuration files (r1215089) + * properly handle errors during password caching (r1380695) + * fix svnversion output not always a number (issue #4226) + * fix conflict resolver losing executable bit of a file (r1391019) + * fix redundant notifications when merging with ra_serf (issue #3802) + * fix 'svn add --force /path/to/wcroot' should work (issue #4241) + * fix file permissions changed after commit (issue #4331) + * improve handling of http errors in ra_serf (1452792, 1452870) + * include checksum of missing pristines in error message (r1452800) + * fix an assert when merging against a replaced source (issue #4132) + * fix replacement in merge source has incorrect notification (issue #4138) + * improve performance of checkout (r1453791) + * fixed documentation regarding merge source (issue #3247) + * fix merge errors out after resolving conflicts (issue #4316) + * fix delete/move with file external in unversioned dir (issue #4293) + * fix resolving tree conflict with local node missing (r1461848) + * fix invalid read during diff suffix scanning (issue #4339) + * fix assertion when running 'svn log @PREV' (r1462134) + * optimize enumerating configuration options (r1464478) + * revert will now sleep for timestamps if using commit times (r1464769) + * don't allow externals to be deleted with 'svn rm' (r1464992) + * improved memory usage in ra_serf and ra_local (r1465280) + * replace some assertions with more helpful error messages (r1465975) + * fixed long keyword expansion truncated (issue #4349) + + - Server-side bugfixes: + * SVNParentPath / repository listing now authz-filtered (r1408184) + * user/group names in the authz config file are case-sensitive (r1475772) + * limit commit runtime for nodes with very deep histories (r1224836) + * 'svnadmin recover' truncates rep-cache at the right point (issue #4077) + * fix crashes in dumpstream loading with skipped revs (r1214202, r1214216) + * fix 'svn log -g' incorrectly treating rename as merge (issue #4022) + * fix bug where fsfs file-hinting fails (issue #4320) + * don't leak path of repository on server's disk to clients (r1330906) + * remove spurious is-fresh-txn-root from empty revision files (issue #4031) + * fix a stdout handling problem in 'svnlook diff' (r1411971) + * fix erratic behaviour in 'svnlook diff' showing property diffs (r1412224) + * fix inconsistent authz error messages in 'svn log' in svnserve (r1292462) + * fix svndumpfilter for empty paths in included or excluded lists (r1294583) + * make fsfs packing threadsafe (r1376011) + * don't error out on intermittent memcached failures (r1394470) + * fix a ra_svn deadlock with zero-copy server option (r1465622) + + - Other tool improvements and bugfixes: + * 'svnmucc' promoted to first-class supported utility (issue #3308, #4279) + * make 'svnmucc' prompt for log messages (issue #3418) + * rename 'svnauthz-validate' to 'svnauthz' (issue #4284) + * make 'svnauthz' optionally validate user/path access (r1197588) + * fix mailer.py test suite problems (r1449582) + * fix mailer.py not showing dirs with property deletions (r1449582) + * make mailer.py generate Date and Message-ID headers (r1449592) + * new '-?' option support for 'svnmucc' (r1339428) + * provide the repository name to mailer.py (r1439592) + * add '--force-interactive' to svnmucc (r1457789) + * add '--trust-server-cert' to svnmucc (r1458995) + + Developer-visible changes: + - General: + * now require Python 2.5 for tests and dev tools (r1243627) + * now require bzip2 for tests and dev tools (r1148512) + * configure defaults to --without-apache-libexecdir (r1469862) + * support builds with APR pool debugging (r1176894) + * 'make extraclean' is more thorough now (r1149460) + * support for Serf 2 (r1147538) + * introduction of editor v2 (via private APIs only) (r1166332 et al) + * improve SQLite setup for compatibility with OS X 10.7. (r1181666) + * rework switch statement to accomodate OWC compiler limitations (r1204407) + * new --enable-sqlite-compatibility-version configure option (r1201421) + * make test suite LD_LIBRARY_PATH include just-built auth plugins (r1200474) + * packages/ directory removed, contents were outdated and unused (r1442167) + * rename 'makefile.ezt' to 'build-outputs.mk.ezt' (r1444822) + * use expensive compiler optimizations with --enable-optimize (r1445063) + * in Visual C++ builds, move temp files to different directory (r1446416) + * remove --with-ssl and --with-gssapi configure options (r1449023) + * require at least serf 1.2.0 as build dependency (issue #4296) + * fix error tracing to record file/line properly (r1331242) + * add --log-level argument to win-tests.py (r1335461) + * improve GDB pretty-printing of svn types (r1351336, r1364750, r1365035) + * load third-party FS modules (if --enable-runtime-module-search) (r1362434) + * enable running the regression tests over https (r1349699) + * support 'make davautocheck' on OS X (r1421583) + * new '--enable-gcov' configure option (r1416646) + * fix build with Apache HTTPD 2.5 (r1408985) + * allow running the test suite through a http proxy (r1410195) + * don't use non-constant initializers in struct variables (r1412911) + * allow generation of Visual Studio 2012 compatible projects (r1245152) + * nicer pretty-printing of Subversion data types in gdb (r1367262 et al) + * teach serf build on Windows to use static APR/Util and OpenSSL (r1371338) + * add --ssl-cert option to win-tests.py to run tests over https (r1372760) + * don't strip Content-Type header form .po files on Windows (r1380056) + * configure now script auto-detects GNOME keyring (r1387230) + * allow configure to detect BDB on Debian-based Linux distros (r1390633) + * auto-detect serf via pkg-config (r1391662) + * improve queries for compatability with SQLite 3.7.16 (r1455239) + * remove support for in-tree apr, apr-util and apr-memcache (r1456924) + * FSFS caching supports prefixes now (r1462436) + * maintainer mode now prints symbolic error codes (r1465157) + * don't require NLS support for kwallet support (r1466445) + * make Julian happy (r1413030) + + - API changes: + * fix inconsistent handling of log revs without changed paths (issue #3694) + * deprecated SVN_ERR_SQLITE_UNSUPPORTED_SCHEMA (r1173240) + * provide API to clear cached auth credentials (issue #2775) + * improve repository location information in various APIs (issue #4170) + * major rewrite of conflict storage and handling APIs (r1354973 et al) + * hide (deprecate) svn_wc APIs that use editors (r1243339) + * svn_stringbuf_ensure() allocates an extra byte for terminator (r1308966) + * switch and update apis are now more consistent (r1465292) + * deprecated svn_client_merge_reintegrate (r1466742) + * deprecated low level ra_svn apis (r1466907) + + - Bindings: + * star-imports in swig-py only import 'svn_*' symbols (r1303375) + * fix compilation of Perl bindings on Mandriva 2007 (issue #2617) + * new JavaHL testing targets (r1182983) + * enable returning an error on malfunctions for JavaHL (r1366215) + * MacOS X build fix to cope with missing GNOME keyring (r1397844) + * fix swig bindings tests on MacOS X (r1397846) + * fix assertion failure in JavaHL error reporting (r1405922) + * support ruby 1.9 (r1407206) + * JavaHL: Include OSGI Manifest information in svn-javahl.jar (r1234864) + * new svn_auth_set_gnome_keyring_unlock_prompt_func function (r1241554) + * fix svn_txdelta window ops for python bindings (r1389054) + * fix build of Perl bindings with newer versions of SWIG (r1389658) + * add missing API functions to Perl bindings (issue #2646) + * add missing API functions to Python bindings (r1392038 et al) + * add missing API functions to JavaHL bindings (issue #4326) + * fix some reference counting bugs in swig-py bindings (r1464899, r1466524) + + +Version 1.7.10 +(30 May 2013, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.10 + + User-visible changes: + - Client-side bugfixes: + * fix 'svn revert' "no such table: revert_list" spurious error (issue #4168) + * fix 'svn diff' doesn't show some locally added files (issue #3797) + * fix changelist filtering when --changelist values aren't UTF8 (r1475724) + * fix 'svn diff --git' shows wrong copyfrom (issue #4294) + * fix 'svn diff -x-w' shows wrong changes (issues #4133 and #4270, r1427278) + * fix 'svn blame' sometimes shows every line as modified (issue #4034) + * fix regression in 'svn status -u' output for externals (r1434750) + * fix file permissions change on commit of file with keywords (issue #4331) + * improve some fatal error messages (r1465975) + * fix externals not removed when working copy is made shallow (issue #3741) + + - Server-side bugfixes: + * fix FSFS repository corruption due to newline in filename (issue #4340) + * fix svnserve exiting when a client connection is aborted (r1482759) + * fix svnserve memory use after clear (issue #4365) + * fix repository corruption on power/disk failure on Windows (r1483781) + + Developer-visible changes + - General: + * make get-deps.sh compatible with Solaris /bin/sh (r1451678) + * fix infinite recursion bug in get-deps.sh (r1421541, r1424977) + * fix uninitialised output parameter of svn_fs_commit_txn() (r1461743) + + - Bindings: + * fix JavaHL thread-safety bug (r1476359) + + +Version 1.7.9 +(04 Apr 2013, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.9 + User-visible changes + - Client-side bugfixes: + * improved error messages about svn:date and svn:author props. (r1440620) + * fix local_relpath assertion (issue #4257) + * fix memory leak in `svn log` over svn:// (r1458341) + * fix incorrect authz failure when using neon http library (issue #4332) + * fix segfault when using kwallet (r1421103) + + - Server-side bugfixes: + * svnserve will log the replayed rev not the low-water rev. (r1461278) + * mod_dav_svn will omit some property values for activity urls (r1453780) + * fix an assertion in mod_dav_svn when acting as a proxy on / (issue #4272) + * improve memory usage when committing properties in mod_dav_svn (r1443929) + * fix svnrdump to load dump files with non-LF line endings (issue #4263) + * fix assertion when rep-cache is inaccessible (r1422100) + * improved logic in mod_dav_svn's implementation of lock. (r1455352) + * avoid executing unnecessary code in log with limit (r1459599) + + Developer-visible changes: + - General: + * fix an assertion in dav_svn_get_repos_path() on Windows (r1425368) + * fix get-deps.sh to correctly download zlib (r13520131) + * doxygen docs will now ignore prefixes when producing the index (r1429201) + * fix get-deps.sh on freebsd (r1423646) + + - Bindings: + * javahl status api now respects the ignoreExternals boolean (r1435361) + + +Version 1.7.8 +(17 Dec 2012, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.8 + User-visible changes + - Client- and server-side bugfixes: + * Fix typos in pt_BR, es and zh_TW translations (r1402417, r1402421) + + - Client-side bugfixes: + * fix crash with --username option on Windows (r1396285) + * add missing attributes to "svn log -v --xml" output (r1398100) + * fix svn patch ignoring hunks after no trailing newline (r1399174) + * fix hang with ra_serf during error processing (r1403583) + * ignore file externals with mergeinfo when merging (r1401915) + * fix "svnmucc cp" segfault with a missing last argument (issue #4079) + * fix conflict handling on symlinks (issue #4091) + + - Server-side bugfixes: + * properly detect threading availability (r1398325) + * fix "svnadmin load --bypass-prop-validation" (r1237779) + * fix parsing of [groupsfoo] sections in authz file (issue #3531) + * add Vary: header to GET responses to improve cacheability (r1390653) + * fix fs_fs to cleanup after failed rep transmission (r1403964, et al) + * fix mod_dav_svn to complain about revisions > HEAD (r1403588) + + Developer-visible changes: + - General: + * fix incorrect status returned by 1.6 API (r1403258) + * fix compilation with g++ 4.7 (r1345740) + * fix svn_uri_get_file_url_from_dirent on Windows (r1409146) + + +Version 1.7.7 +(09 Oct 2012, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.7 + User-visible changes + - Client- and server-side bugfixes: + * fix memory read bug (r137614) + * update Chinese translation + + - Client-side bugfixes: + * fix issues with applying Git patch files (r1374800, et al) + * fix status does not descend into dir externals after upgrade (issue #4016) + * fix file externals don't update with old mod_dav_svn (issue #4224) + * fix external diff tool duplicates Index: lines with 'svn diff' (r1380697) + * fix GNOME keyring library fails with very old glib (r1378847) + * fix unknown password stores in config file cause error (r1375052) + * fix assertions in ra_serf running against server root (r1365519, et al) + * fix ra_serf checkout/export aborts early on Windows (issue #4174) + + - Server-side bugfixes: + * fix an assert with SVNAutoVersioning in mod_dav_svn (issue #4231) + * fix unbounded memory use with SVNPathAuthz short_circuit (r1387943) + * fix svndumpfilter exclude --targets requires leading slash (issue #4234) + * fix connection ttl for memcache should be 50 seconds (r1391641) + * stabilize order of paths in dumpfiles with APR 1.4.6 (r1344864, et al) + + Developer-visible changes: + - General: + * print "All tests successful" at the end of 'make check' (r1375089) + * fix sandbox violation in a test (r1371282) + * fix tests fail when running within a format 30 WC (r1391188, et al) + * fix return value of svn_client_update4() incorrect (r1380295) + * fix make check summary missing test failures (r1390965) + * fix build does not fail when apache httpd is not available (r1374198) + + - Bindings: + * fix swig-pl build fails with swig 2.0.7 and newer. (r1389658) + * fix swig-py runtime problems with swig 2.0.5 and newer (r1351117) + + +Version 1.7.6 +(15 Aug 2012, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.6 + + User-visible changes: + - Client- and server-side bugfixes: + + - Client-side bugfixes: + * Fix "svn status -u --depth empty FILE" (r1348822, r1349215) + * Fix example output of 'svn help status' (issue #3962) + * propset of svn:eol-style might not notice related text changes (r1353572) + * sort output of 'svn propget -R' (r1355699) + * sort output of 'svn proplist' (r1355698) + * sort output of 'svn status' (r1341012) + * avoid a filestat per working copy find operation (r1340556) + * optimize 'svn upgrade' performance on large working copies (r1342984) + * allow 'file:///C:\repos' style arguments on Windows, like 1.6 (r1346765) + * fix ra_serf against Subversion 1.2 servers (r1349367) + * fix 'svn upgrade' on working copies with certain tree conflicts (r1345482) + * avoid workqueue references to system temp dir (r1367854) + * allow non-existant canonical paths (r1367853) + * fix 'svn revert --depth files' to operate on files (r1365554) + * fix ra_serf XML namespace handling against malicious server (r1337441) + * fix relocate with server-relative externals (issue 4216) + * change two asserts into errors for TortoiseSVN (r1368128, r1368065) + * don't attempt to anchor an operation outside a wc root (r1361341) + + - Server-side bugfixes: + * partial sync drops properties when converting to adds (issue #4184) + * replaying a copy and delete of an unreadable child fails (issue #4121) + * allow svnlook to operate on r0 (r1362508) + * make FSFS revision files independent of APR hash order (r1367498) + + - Other tool improvements and bugfixes: + * move mod_dontdothat to install-tools (r1307177) + + Developer-visible changes: + - General: + * fix running tests against httpd 2.4 (r1291594) + * use constant struct initialisers for C89 compatibility (r1352068) + + - Bindings: + * JavaHL: Don't assert on some invalid input (r1354626, r1354652) + * JavaHL: Add missing new in 1.7 notifications (r1351772) + + +Version 1.7.5 +(17 May 2012, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.5 + + User-visible changes: + - Client- and server-side bugfixes: + * http: report deleted-revision upon delete during update (r1327474) + + - Client-side bugfixes: + * avoid potential segfault when canonicalizing properties (r1296369) + * improve memory and file-handle management with externals (issue #4130) + * serf: convert assertions to "MERGE failed" errors (r1302417) + * fix undefined behaviour during multi-segment reverse merges (issue #4144) + * fix potential use of already freed memory during diff operation (r1311935) + * improve performance of scan for the working copy root (r1306334) + * cmdline: fix segfault during 'svn diff' argument processing (r1311702) + * fix regression from 1.6 in update with --depth option (issue #4136) + * propset: avoid undefined behaviour in error path (r1325361) + * reset sqlite statements, partly for sqlite-3.7.11 compat (r1328846, et al) + * fix assertion during 'svn diff -r BASE:HEAD ^/trunk' (issue #4161) + * notify upon 'update' just removing locks on files (r1329876) + * neon: fix potential use of freed memory during commits (r1329388) + * 'status --xml' doesn't show repository deletes correctly (issue #4167) + * fix assert on svn:externals with drive letter on Windows (issue #4073) + * fix 'svn update --depth=empty' against 1.4 servers (issue #4046) + * handle missing svn:date reported by svnserve gracefully (r1306111) + * fix merges which first add a subtree and then delete it (issue #4166) + * fix a regression with checkout of file externals (issue #4087) + * don't add spurious mergeinfo to subtrees in edge-case merge (issue #4169) + * improve performance of status on large working copies (issue #4178) + + - Server-side bugfixes: + * fix non-fatal FSFS corruption bug with concurrent commits (issue #4129) + * mod_dav_svn: raise an error on MERGE of non-existent resource (r1298343) + * mod_dav_svn: support compiling/running under httpd-2.4 (r1232267) + * mod_dav_svn: forbid BDB repositories under httpd's event MPM (issue #4157) + + - Other tool improvements and bugfixes: + * emacs support: updates to dsvn.el and vc-svn.el (r1200896, et al) + + Developer-visible changes: + - General: + * windows example distribution scripts: include svnrdump (r1295007) + * fix running the test suite with jsvn (r1335555) + + - Bindings: + * swig-py tests: avoid FAILs on APR hash order (r1296137, r1292248) + * swig-rb tests: avoid FAILs on APR hash order (r1310535, r1310594) + * swig-pl: Improved perl detection in gen-make.py (r1291797, r1291810) + + +Version 1.7.4 +(08 Mar 2012, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.4 + + User-visible changes: + * fix 'svn log --diff' for moved paths (r1210147, et al) + * fix ra_serf problem with reading directory entries via HTTPv2 (r1238121) + * prepend "sqlite:" to error messages from SQLite (r1245738, -817) + * fix randomly missing "Merged via" notifications in 'svn log -g' (r1293229) + * fix spurious conflict when merging deleted symbolic link (issue #4052) + * fix URL-to-WC copy of externals on Windows (issue #4123) + * improve an FSFS sanity-check error message (r1294470) + * fix regressions with symlinks pointing at externals (issue #4102) + * fix 'svn log --diff' output ordering issue on Windows (r1295671) + + Developer-visible changes: + * don't build mod_dontdothat if not building with httpd (r1243976) + * fix the testsuite to avoid FAILs on APR hash order (r1230714, et al) + + +Version 1.7.3 +(14 Feb 2012, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.3 + + General: + * ship mod_dontdothat with the standard release + + User-visible changes: + * fix segfault on 'svn rm $ROOT_URL' (issue #4074) + * replace a couple of assertions in favor of errors (r1207858, -949) + * fix a server assert after being upgraded (r1210195) + * fix segfault on 'svn mkdir svn://localhost' (r1211483) + * make 'svnadmin recover' prune the rep cache (r1213331, et al) + * make svnmucc use values from --config-dir option + * update and clarify the merge help text (r1154121, et al) + * replace wc assertion with informative error (r1222521, -693) + * copy permissions correctly for FSFS dirs (r1229252) + * improve 'svn log --with-all-revprops' over ra-dav (issue #4082) + * fix segfault when remapping a file external (issue #4093) + * fix segfault caused by obstructing unversioned dir (r1229677) + * fix regression on first update of external dir with '-r' (issue #4053) + * fix various EOL-handling problems in 'svn patch' (issues #3814, #3991) + * fix segfault in 'svn revert' (r1229303) + * improve correctness of 'svn patch --dry-run' (r1231944, -5) + * enforce revisions given in 'svn:externals' (issue #4053) + * fix potential corruption on 32-bit FSFS with large files (r1230212) + * make 'svn status --xml' show new files (issue #4097) + * fix 'svn mergeinfo' correctness (issue #4050) + * return the correct status for non-present nodes (r1232202, -07, -21, -22) + * improve SASL error messages (r1236343, et al) + * improve server cert error code for ra_serf (r1232413) + * fix SVNParentPath listings for parent path symlinks (r1221767, -80) + * fix mod_dav_svn's handling of POST errors (issue #4086) + * log some mod_dav_svn errors, rather than ignoring them (r1237720, -9596) + * relax requirements for canonicalization in mod_dav_svn (r1236173) + * fix a rare source of FSFS corruption (r1240752) + * allow committing the result of some copy operations (issue #4059) + * prevent one-byte buffer overflow in base64 decoding (r1242337) + + Developer-visible changes: + * JavaHL: Add missing notify action, fixing an exception (r1221793) + * fix swig-py memory leak (r1235264, -296, -302, -736) + * fix spurious test suite failure (r1220742, -50) + * allow running tests on UNC shares (r1225491) + * bindings: see platform-specific password providers (r1242660, -1) + * skip 'svnrdump dump' tests over ra_serf (r1242537) + * convert a few ra_serf assertions to errors (r1242607) + + +Version 1.7.2 +(02 Dec 2011, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.2 + + User-visible changes: + * fix working copy corruption after interrupted update/switch (issue #4040) + * avoid segfaults against pre-1.5 servers (r1186928) + * improve configure error message if apr-util uses old or no bdb (r1186784) + * make 'svn patch' ignore '/dev/null' targets for compat with git (r1197998) + * fix 'svn patch' segfault on patch that skips and deletes files (r1199950) + * omit "Committed revision N." output from 'svn commit --quiet' (r1200837) + * fix authz denial when svnserve root is a repository (issue #4060) + * fix uninitialized memory read in client diff code (r1201002) + * avoid potential segfault during merges (r1202807) + * fix an assertion failure when a symlink is updated (r1186944, -81, -83) + * make working copy operations fail if nodes have no base checksum (r1202630) + * fix nested s when using v2 protocol (r1203546, -651, -653) + * make mod_dav_svn ignore non-Subversion POST requests (r1187695) + * avoid reading freed memory (r1204478) + * recognize empty (only byte order mark) UTF-8 files as text (issue #4064) + * fix 1.7 client regression when operating against a 1.0.x server (r1199876) + * remove empty parent dirs of removed externals on update (issue #4044) + * make 'svn diff -c N' work for files added in rN (issue #2873) + * plug a memory leak in the bdb backend (r1205726) + * fix 'svn import' with native eol-style and inconsistent EOLs (r1205193) + * fix reading beyond the end of a string in bdb backend (r1205839, -48) + * don't assert when committing an incomplete directory (issue #4042) + + Developer-visible changes: + * JavaHL: allow 'status -u' to function properly (r1189190, -395) + * don't put '\r' characters in our generate sql headers (r1189580) + * properly define WIN64 on Windows x64 builds (r1188609) + * better adherence to C89 in enum definitions (r1189665) + * bump copyright year in Windows DLLs (r1189261) + * log a better error when opening rep-cache.db fails (r1204610, -73) + + +Version 1.7.1 +(24 Oct 2011, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.1 + + User-visible changes: + * improve performance of 'svn info' (r1164386) + * improve hash table sorting performance (r1167659) + * update bash completion for 1.7 (r1177001) + * make 'svn ls' continue to work with 1.0-1.3 repos (r1154278, -379, -82) + * improve handling of error messages generated by Cyrus SASL (r1179767) + * update INSTALL documentation file (r1182115, and others) + * error instead of assert when upgrading corrupt WCs (r1182904, -9) + * improve memory usage in merge (r1176915) + * fix an invalid assertion in merge (r1149103, -35) + * improve performance of 'merge --reintegrate' in edge-case (r1167681) + * fixed: 'svn mergeinfo' shows wrong revisions for added nodes (issue #3791) + * make 'svn add --parents D/file' work if D is deleted (r1185222) + * improve performance of trivial text file merges (issue #4009) + * add FSFS sanity check to prevent corruption seen in the wild (r1178280) + * improve correctness/performance of recursive info and proplist (r1164426) + * fix memory leak in 'merge --reintegrate' (r1180154) + * fix handling of directories after 'update --set-depth=empty' (r1185911) + * fix 'checksum != NULL' assertions in some upgraded WCs (r1177732) + * fix upgrading of WCs containing authz-restricted dirs (r1185738) + * make the server tolerate svn:mergeinfo with malformed paths (r1182771) + * fix some erroneous warning messages from the svn client (r1185746) + * fix WC upgrade with replaced nodes in edge-case (issue #4033) + + Developer-visible changes: + * fix object lifetime issues in the JavaHL bindings (r1175888) + * fix org.tigris JavaHL wrappers to avoid double finalize (r1179680) + * don't write to const memory (r1177492) + * improve zlib configuration (r1174761, -98, -806) + * improve SQLite runtime init for OS X 10.7 compat (r1181666) + * improve test suite correctness (r1174111) + * fix potential segfault seen by TSVN (r1183263) + * fix backward compat crashes in JavaHL (r1183054, -347) + * fill in repos_* fields of svn_wc_status3_t for repos-only nodes (r1181609) + * disable the SQLite shared process cache (r1185242, r1185280) + + +Version 1.7.0 +(11 Oct 2011, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.0 + +See the 1.7 release notes for a more verbose overview of the changes since +the 1.6 release: http://subversion.apache.org/docs/release-notes/1.7.html + + User-visible changes: + - General: + * No longer including contrib/ in the release tarballs (r877798) + + - Major new features: + * Less verbose HTTP-based repository access protocol (issue #1161, #3371) + * Rewritten working copy metadata storage (issue #3357) + * New 'svn patch' subcommand (issue #511) + * Rewritten FSFS in-memory caching for better performance + * New remote repository dump/load client 'svnrdump' + + - Minor new features and improvements: + * Better handling of HTTP redirects (issue #2779) + * Improved and much more consistent path handling (issue #2028, and others) + * 'svnadmin load' rewrites changed revnums in mergeinfo (issue #3020) + * Error message and help text improvements + * 'svn log' can print unidiff of changes made in a revision (issue #2909) + * 'svn diff' can print git-style unidiff annotations + * svnsync can now steal locks on a mirror repository (issue #3309) + * display the wc root in the output of 'svn info' (issue #3355) + * add 'svnlook filesize' (issue #3509) + * add 'svn upgrade' command for upgrading working copies (r877675) + * add 'svnsync --disable-locking' (issue #3545) + * subtree merges don't unconditionally stop reintegrate merge (issue #3577) + * 'svn relocate' replaces 'svn switch --relocate' (r1026475) + * 'svn relocate' updates relative externals (issue #3597) + * allow svnsync users to specify the source repo (issue #3637) + * remove redundant mergeinfo notifications for 2-URL merges (issue #3671) + * 'svn export' into the current directory (issue #3727) + * added '--parents' to 'svn update' (issue #3748) + * allow configurable connection timeout in ra_serf (r876161) + * add digest authentication in ra_serf (r876405) + * add extensive caching support to servers (r1067669, -75, -72302) + * add configurable caching to svnadmin (r1078357) + * make server-side network data compression rate configurable (r1072288) + * added support for auto-detecting mime-types with libmagic (r1131120) + * 'svn rm url1 url2 url3' uses single txn per repo (issue #1199) + * don't leave unversioned files when reverting copies (issue #3101) + + - Client-side bugfixes: + * 'svn cp A B; svn mv B C' is equivalent to 'svn cp A C' (issue #756) + * revert fetches missing directories from the server (issue #1040) + * allow subdirs of moved dirs to be moved and committed (issue #1259) + * improved performance of 'svn mv' with whole directories (issue #1284) + * 'svn rm B; svn cp A B' now works (issue #1516) + * 'svn diff URL1 URL2' now reverse of 'svn diff URL2 URL1' (issue #2333) + * error if relocating to an unused URL (issue #2531) + * 'svn blame -rWORKING' is now supported (issue #2544) + * improve correctness of commit on a relocated wc over ra_dav (issue #2578) + * add early error to 'svn add --auto-props' with mixed eols (issue #2713) + * allow 'svn diff' to accept symlinks as targets (issue #2716) + * don't lose props for replaced items (issue #2743) + * handle mergeinfo for subtrees removed outside of svn (issue #2915) + * add ability to force 'svn diff' to use internal diff (issue #3701) + * correctly recover a schedule-for-delete rm'd outside of svn (issue #3106) + * don't create self-referential mergeinfo from own history (issue #3157) + * improve 'svn log -g' handling of bad mergeinfo source paths (issue #3270) + * better conflict stat printing (issue #3342, issue #3594) + * 'svn update' restores excluded files (issue #3544) + * allow reintegrate merges into WCs with missing subtrees (issue #3603) + * more gracefully error when given back cmdline input (issue #3620) + * update exit codes to reflect command failure (issue #3622) + * don't double-update file externals (issue #3665) + * improve output of multi-target update (issue #3693, #3746) + * make 'svn up --set-depth=exclude FILE' work (issue #3736) + * return correct error code for 'svn cat' on nonexisting file (issue #3713) + * support svn:externals on locally added directories (issue #2267) + * use installed GSSAPI lib for Kerberos in ra_serf (r877381) + * allow 'svn info' to run on an excluded item (issue #3792) + * improve 'log -g' output with reverse merges (issue #3176) + * don't print error message if stdout is a pipe and is closed (issue #3014) + * removed special copy-handling during updates added in 1.5.0 (issue #3711) + * fix warning about copies committed with non-infinity depth (issue #3752) + * can now commit multiple wc paths lacking a common parent (issue #2381) + * 'svn export --depth $WC' now works correctly (issue #3800) + * added support for case-only renames on Windows (issue #3702) + * 'svn delete --force' removes tree conflicts (issue #3805) + * don't throw an error when skipping tree conflicts in update (issue #3329) + * don't break commits of wc->wc copies with file externals (issue #3589) + * allow 'svn info' to work on symlinks to working copies (issue #2305) + * allow 'svn st --show-updates' to work across symlinks (issue #3117) + * 'svn revert' shouldn't loop on symlinks (issue #3972) + * fixed: wc-to-wc copy of a switch source (issue #1802) + * fixed: 'svn st' reports symlinks as obstructed items (issue #2284) + * fixed: 'cd e:\; svn up e:\' fails (issue #2556) + * fixed: svn aborts on commiting from root dir on windows (issue #3346) + * fixed: removing a dir scheduled for deletion corrupts wc (issue #2741) + * fixed: 'svn cleanup' fails on obstructed paths (issue #2867) + * fixed: case-only renames resulting from merges don't work (issue #3115) + * fixed: 'svn mergeinfo' ignores peg rev for wc target (issue #3180) + * fixed: unable to merge to wc of deleted branch (issue #3221) + * fixed: move via merge leaves behind versioned move source (issue #3324) + * fixed: ra_serf does not honor http-proxy-exceptions (issue #3428) + * fixed: 'svn mv A B; svn mv B A' loses history (issue #3429) + * fixed: ra_serf doesn't support http-auth-types config (issue #3435) + * fixed: merge sets incorrect mergeinfo on skipped paths (issue #3440) + * fixed: ra_serf inconsistent handling of cached authn creds (issue #3450) + * fixed: ra_serf sefault with using NTLM or Negotiate auth (r876910) + * fixed: excluded subtrees are not detected by svnversion (issue #3461) + * fixed: submitting a changelist while obstructed item exists (issue #3484) + * fixed: crash when changing an external's URL (issue #3530) + * fixed: target moved after branching breaks reintegrate (issue #3640) + * fixed: potential race condition in svnsync (issue #3546) + * fixed: spurious merge conflicts with pre-1.7 mod_dav_svn (issue #3657) + * fixed: repeat merge is not a no-op (issue #3564) + * fixed: inheritance results in self-referential mergeinfo (issue #3668) + * fixed: inheritance results in nonexistent mergeinfo sources (issue #3669) + * fixed: memory leaks in ra_serf (issue #3684) + * fixed: corruption of 'svn pg' output for large properties (issue #3721) + * fixed: 'svnsync copy-revprops' doesn't sync revprop dels (issue #3728) + * fixed: executable flag not correctly set on merge (issue #3686) + * fixed: 'svn rm' fails on multiple URLs with encoded spaces (issue #3839) + * fixed: children of replaced dirs cannot be deleted (issue #3468) + * fixed: executable flag of binary file lost during merge (issue #3686) + * fixed: merging a symlink-turned-regular-file breaks the wc (issue #2530) + * fixed: can't remove file externals (issue #3351) + * fixed: 'svn unlock' attempts to unlock wrong token on DAV (issue #3794) + * fixed: forced DAV 'svn unlock' results in 403, not warning (issue #3801) + * fixed: rm -> ci -> cp = missing directory (issue #2763) + * fixed: 'svn info' returns parent info on missing dirs (issue #3178) + * fixed: spurious prop conflict with 'merge --reintegrate' (issue #3919) + * fixed: 'svn --version' fails with non-existant $HOME (issue #3947) + * fixed: unforced export silently overwites existing file (issue #3799) + * fixed: reverse merge which adds subtree mergeinfo fails (issue #3978) + * fixed: 'svn up -r{R>HEAD}' hangs client over ra_svn (issue #3963) + * fixed: 'svn up' updates file externals in target siblings (issue #3819) + * many other minor bugfixes, optimizations, plugs of memory leaks, etc + + - Server-side bugfixes: + * mod_dav_svn is less strict about auto-merging for commits (issue #1704) + * allow SVNListParentPath to be used with authz (issue #2753) + * allow nav to repo list from repo top with SVNListParentPath (issue #3159) + * allow repositories in the root of a drive on windows (issue #3535) + * don't destroy mergeinfo with 'svnadmin load --parent-dir' (issue #3547) + * fixed: 'svnadmin hotcopy' does not duplicate symlinks (issue #2591) + * fixed: post-revprop-change errors cancel commit (issue #2990) + * fixed: mod_dav_svn runs pre-revprop-change hook twice (issue #3085) + * fixed: mod_dav_svn doesn't return stderr to user on failure (issue #3112) + * fixed: hotcopy may corrupt target rep-cache.db (issue #3596) + * fixed: mod_dav_svn can cause spurious merge conflicts (issue #3657) + * fixed: DAV can overwrite directories during copy (issue #3314) + * fixed: 'svn log' returns log of unrelated path (issue #3931) + * match paths against authz rules in case sensitive way (issue #3781) + * svnserve can now force usernames to upper/lower case (issue #3726) + * reduce duplicate log messages in 'log -g' (issue #3650) + * svnserve: don't crash on shutdown with SASL in inetd mode (issue #3664) + * disallow arbitrary HTTP headers from committers (issue #2872) + * limit FSFS memory consumption (issue #3478, #3593) + * many other minor bugfixes too numerous to list here + + - Other tool improvements and bugfixes: + * svnsync now takes the '--config-option' argument (issue #2027) + * svnsync can translate non-UTF-8 properties to UTF-8 (issue #3817) + * svnadmin now errors on non-UTF-8 revision properties (issue #3755) + * svnadmin verify now errors on non-UTF-8 paths (r1129641) + + Developer-visible changes: + - General: + * improved output of 'make check' + * introduce scratch_pool/result_pool parameter paradigm + * improved error tracing (r877208, -736) + * improve building with sqlite on Windows (issue #3364) + * allow mod_dav_svn to compile against Apache 2.4 (issue #3548) + * support running tests against older servers (r876016) + * notification of unversioned obstructions (r877344) + * removed virtually all abort() calls (issue #2780) + * don't include client-specific suggestions in error msgs (issue #3887) + + - API changes: + * don't crash svn_client_copy if ctx->log_msg_func is NULL (issue #3234) + * much improved ra_serf error handling (issue #3375) + * provide clients with old and new revision on update (r876515) + * close both files, even on error in svn_stream_copy3() (r887262) + * added 'work-in-progress' XFail test status (r876549) + * notifications sent when mergeinfo changes (r877588) + * add information on text and property mods in log APIs (r877688) + * fixed: svn_ra_local__get_file() leaks file descriptors (issue #3290) + * svn_ra_neon__get_dir() returns correct dir set for URLs (issue #3093) + * swig-py: always set ChangedPath.path (also for deletes) (issue #2630) + * improve conflict resolver API for a specific direction (issue #3049) + + - Bindings: + * New JavaHL package: org.apache.subversion + * Deprecate the SVNClientSynchronized class in JavaHL (issue #2755) + * fixed setting binary properties in JavaHL (issue #3770) + * fix type mapping of svn_txdelta_window_t in python bindings (issue #3688) + + +Version 1.6.23 +(30 May 2013, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.23 + + User-visible changes + - Server-side bugfixes: + * fix FSFS repository corruption due to newline in filename (issue #4340) + * fix svnserve exiting when a client connection is aborted (r1482759) + + - Other tool improvements and bugfixes: + * fix argument processing in contrib hook scripts (r1485350) + + +Version 1.6.22 +(Not released, see changes for 1.6.23.) + + +Version 1.6.21 +(04 Apr 2013, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.21 + + User-visible changes + - Server-side bugfixes: + * mod_dav_svn will omit some property values for activity urls (r1453780) + * improve memory usage when committing properties in mod_dav_svn (r1443929) + * fix mod_dav_svn runs pre-revprop-change twice (issue #3085) + * fixed: post-revprop-change errors cancel commit (issue #2990) + * improved logic in mod_dav_svn's implementation of lock. (r1455352) + + Developer-visible changes: + - General: + * fix a compatibility issue with g++ 4.7 (r1345740) + + +Version 1.6.20 +(04 Jan 2013, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.20 + + User-visible changes + - Client- and server-side bugfixes: + * Fix typos in pt_BR, es and zh_TW translations (r1402417) + + - Server-side bugfixes: + * add Vary: header to GET responses to improve cacheability (r1390653) + * fix fs_fs to cleanup after failed rep transmission (r1403964, et al) + * fix an assert with SVNAutoVersioning in mod_dav_svn (issue #4231) + + +Version 1.6.19 +(10 Sep 2012, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.19 + + - Client-side bugfixes: + * handle missing svn:date reported by svnserve gracefully (r1306111) + + - Server-side bugfixes: + * fix possible server hang if a hook script fails to start (r1330410) + * fix write-through proxy commit regression introduced in 1.6.17 (r1088602) + * partial sync drops properties when converting to adds (issue #4184) + + - Developer-visible changes: + * fix the testsuite to avoid FAILs on APR hash order (r1230714, et al) + + +Version 1.6.18 +(29 Mar 2012, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.18 + + User-visible changes: + * reject invalid svn:mergeinfo at commit time over DAV (issue #3953) + * fix reintegrate merge regression introduced in 1.6.13 (issue #3957) + * make the stderr output of the post-commit hook XML-safe (r893478) + * fix a rare source of FSFS corruption (r1240752) + * plug a memory leak in the bdb backend (r1205726) + * server-side performance fix for "log -g" (r1152282) + * fix description of svndumpfilter's --targets option (r1151911) + * fix datastream corruption during resumed transfer in ra_serf (r1154733) + * fix a crash in ra_svn SASL authentication (r1166555, -678) + * fix potential corruption on 32-bit FSFS with large files (r1230212) + * make website links point to subversion.apache.org (r896893, -901, r915036) + * fix non-fatal FSFS corruption bug with concurrent commits (issue #4129) + + Developer-visible changes: + * fix sqlite distfile retrieval in get-deps.sh (r1134734) + * fix swig-py memory leak (r1235264, -296, -302, -736) + * allow passing --with-jdk to gen-make.py on Windows (r966167) + + +Version 1.6.17 +(01 Jun 2011, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.17 + + User-visible changes: + * improve checkout speed on Windows (issue #3719) + * make 'blame -g' more efficient with large mergeinfo (r1094692) + * avoid some invalid handle exceptions on Windows (r1095654) + * preserve log message with a non-zero editor exit (r1072084) + * fix FSFS cache performance on 64-bit platforms (r1103665) + * make svn cleanup tolerate obstructed directories (r1091881) + * fix deadlock in multithreaded servers serving FSFS repositories (r1104093) + * detect very occasional corruption and abort commit (issue #3845) + * fixed: file externals cause non-inheritable mergeinfo (issue #3843) + * fixed: file externals cause mixed-revision working copies (issue #3816) + * fix crash in mod_dav_svn with GETs of baselined resources (r1104126) + See CVE-2011-1752, and descriptive advisory at + http://subversion.apache.org/security/CVE-2011-1752-advisory.txt + * fixed: write-through proxy could direcly commit to slave (r917523) + * detect a particular corruption condition in FSFS (r1100213) + * improve error message when clients refer to unkown revisions (r939000) + * bugfixes and optimizations to the DAV mirroring code (r878607) + * fixed: locked and deleted file causes tree conflict (issue #3525) + * fixed: update touches locked file with svn:keywords property (issue #3471) + * fix svnsync handling of directory copyfrom (issue #3641) + * fix 'log -g' excessive duplicate output (issue #3650) + * fix svnsync copyfrom handling bug with BDB (r1036429) + * server-side validation of svn:mergeinfo syntax during commit (issue #3895) + * fix remotely triggerable mod_dav_svn DoS (r1130303) + See CVE-2011-1783, and descriptive advisory at + http://subversion.apache.org/security/CVE-2011-1783-advisory.txt + * fix potential leak of authz-protected file contents (r1130303) + See CVE-2011-1921, and descriptive advisory at + http://subversion.apache.org/security/CVE-2011-1921-advisory.txt + + Developer-visible changes: + * fix reporting FS-level post-commit processing errors (r1104098) + * fix JVM recognition on OS X Snow Leopard (10.6) (r1028084) + * allow building on Windows with recent Expat (r1074572) + + +Version 1.6.16 +(02 Mar 2011, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.16 + + User-visible changes: + * more improvement to the 'blame -g' memory leak from 1.6.15 (r1041438) + * avoid a crash in mod_dav_svn when using locks (r1071239, -307) + See CVE-2011-0715, and descriptive advisory at + http://subversion.apache.org/security/CVE-2011-0715-advisory.txt + * avoid unnecessary globbing for performance (r1068988) + * don't add tree conflicts when one already exists (issue #3486) + * fix potential crash when requesting mergeinfo (r902467) + * don't attempt to resolve prop conflicts in 'merge --dry-run' (r880146) + * more fixes for issue #3270. + + Developer-visible changes: + * ensure report_info_t is properly initialized by ra_serf (r1058722) + * locate errors properly on a malfunction (r1053208) + * fix output param timing of svn_fs_commit_txn() on fsfs (r1051751) + * for svn_fs_commit_txn(), set invalid rev on failed commit (r1051632, -8) + * fix sporadic Ruby bindings test failures (r1038792) + * fix JavaHL JVM object leak when dumping large revisions (r947006) + * use Perl to resolve symlinks when building swig-pl (r1039040) + * allow Perl bindings to build within a symlinked working copy (r1036534) + * don't overwrite the LD_LIBRARY_PATH during make check-swig-pl (r946355) + * improve unit tests for some fs functions (r1051744, -5, -3185, -241) + + +Version 1.6.15 +(26 Nov 2010, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.15 + + User-visible changes: + * hide unreadable dirs in mod_dav_svn's GET response (r996884) + * make 'svnmucc propsetf' actually work (r1005446) + * limit memory fragmentation in svnserve (r1022675) + * fix 'svn export' regression from 1.6.13 (r1032970) + * fix 'svn export' mistakenly uri-encodes paths (issue #3745) + * fix server-side memory leaks triggered by 'blame -g' (r1032808) + This has been tracked as CVE-2010-4644 + * prevent crash in mod_dav_svn when using SVNParentPath (r1033166) + This has been tracked as CVE-2010-4539 + * allow 'log -g' to continue in the face of invalid mergeinfo (issue #3270) + * filter unreadable paths for 'svn ls' and 'svn co' (r997026, -070, -474) + * fix abort in 'svn blame -g' (issue #3666) + * fix file handle leak in ruby bindings (issue #3512) + * remove check for 1.7-style working copies (issue #3729) + + Developer-visible changes: + * improve some swig parameter mapping (r984565, r1035745) + * improve test accuracy over dav (r991534, r877814) + * create fails.log for test runs (r964349) + * improve detection of 'svnversion' when building (r877219, et al) + * don't violate API layering in dumpstream logic (issue #3733) + * don't report working copy installs as switched (r1033921) + + +Version 1.6.14 +(Not released, see changes for 1.6.15.) + + +Version 1.6.13 +(01 Oct 2010, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.13 + + User-visible changes: + * don't drop properties during foreign-repo merges (issue #3623) + * improve auto-props failure error message (r961970) + * improve error message for 403 status with ra_neon (r876615) + * don't allow 'merge --reintegrate' for 2-url merges (r959004) + * improve handling of missing fsfs.conf during hotcopy (r980811, -1449) + * escape unsafe characters in a URL during export (issue #3683) + * don't leak stale locks in FSFS (r959760) + * better detect broken working copies during update over ra_neon (r979045) + * fsfs: make rev files read-only (r981921) + * properly canonicalize a URL (r984928, -31) + * fix wc corruption with 'commit --depth=empty' (issue #3700) + * permissions fixes when doing reintegrate merges (related to issue #3242) + * fix mergeinfo miscalculation during 2-url merges (issue #3648) + * fix error transmission problems in svnserve (r997457, -66) + * fixed: record-only merges create self-referential mergeinfo (issue #3646) + * fixed: 'SVNPathAuthz short_circuit' unsolicited read access (issue #3695) + See CVE-2010-3315, and descriptive advisory at + http://subversion.apache.org/security/CVE-2010-3315-advisory.txt + * make 'svnmucc propset' handle existing and non-existing URLs (r1000607) + * add new 'propsetf' subcommand to svnmucc (r1000612) + * warn about copied dirs during 'svn ci' with limited depth (r1002094) + + Developer-visible changes: + * make ruby bindings compatible with Ruby 1.9 (r957507) + * use the repos verify API in JavaHL (r948916) + * teach ra_serf to parse md5 checksums with update editors (r979429) + * let ra_serf work with current serf releases (r879757, r880320, r943796) + + +Version 1.6.12 +(21 Jun 2010, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.12 + + User-visible changes: + * further improvements for issue #3242 + * allow deletion of uris which need character escaping (issue #3636) + * fix errors with 'svn mkdir --parents' (issue #3649) + * update address to which crash reports are sent (r901304) + * check for server certificate revocation on Windows (r898048) + * disable custom file mutexes on Windows (r879902, -16) + * fix handling of peg revision'd copy targets (issue #3651) + * more improvements to 'svn merge --reintegrate' (r935631) + * allow copying of broken symlinks (issue #3303) + * improve rep-sharing performance on high-concurrency repos (issue #3506) + * fixed: added subtrees with mergeinfo break reintegrate (issue #3654) + * fixed: assertion triggered by tree-conflicted externals (issue #3469) + + Developer-visible changes: + * give windows devs more flexibility with sqlite versions (r944635) + * allow the pack tests to work with low file descriptor limits (r937610) + * improve exception handling on Windows Vista and 7 (r878447, -910, -916) + + +Version 1.6.11 +(19 Apr 2010, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.11 + + User-visible changes: + * fix for repositories mounted via NFS (issue #3501) + * enable TCP keep-alives in svnserve (r880552) + * tighten restrictions on revprops for 'svnadmin verify' (r904594) + * make ra_serf give better out-of-date information (issue #3561) + * improve error message upon connection failure with svn+ssh:// (r922516) + * allow 'svn log' on an uncommitted copy/move destination (r901752) + * make 'svnadmin hotcopy' copy the fsfs config file (r905303) + * mergeinfo improvements with non-inheritable mergeinfo (issue #3573) + * make mergeinfo queries not require access to the repo root (issue #3242) + * update URLs to refer the new apache.org repository (r904301, -94) + * update relative externals during a switch (issue #3390) + * fix 'merge --reintegrate' with self-referential mergeinfo (r892050, -85) + * improve wc-ng working copy detection (r929382) + * improve handling of mergeinfo when using serf (r880461) + * fixed: 'svnlook plist --revprop' with '-t TXN_NAME' (r917640, -8211) + * fixed: file external from URL cannot overwrite existing item (issue #3552) + * fixed: potential memory error in 'svn status' (r923674, -9) + * fixed: merge records mergeinfo from natural history gaps (issue #3432) + * fixed: theoretical possibility of DB corruption (r926151, -67) + + Developer-visible changes: + * disable checks for wc-ng working copies when running the test suite + * on Windows, don't ignore move operation error codes (r896915) + * more precise reporting of errors occuring with sqlite init (r927323, -8) + * ensure rangelist APIs are commutative (r923389, -91) + + +Version 1.6.10 +(Not released, see changes for 1.6.11.) + + +Version 1.6.9 +(25 Jan 2010, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.9 + + User-visible changes: + * allow multiple external updates over ra_svn (issue #3487) + * fix a segmentation fault when using FSFS (r881905) + * support Berkeley DB 4.8 (r879688) + * various autoprop improvements (r880274, -5) + * improve usage of svn+ssh:// on Windows (issue #2580) + * teach 1.6.x to recognize 1.7 working copies (1.6.x-future-proof branch) + * update help text for 'svn update' and 'svn switch' (r886164, -97) + * make 'svnadmin load --parent-dir' create valid mergeinfo (r888979, -9081) + * tolerate relative merge source paths in mergeinfo (r889840) + * teach mod_dav_svn to support the Label header (issue #3519) + * fixed: svnsync leaves stale sync-locks on mirrors (r884842) + * fix applicability of 'svn resolve --accept=theirs-conflict' (r880525, -6) + * fixed: segfault in 'svn resolve' (r896522, -47) + * fix commit failure against an out-of-date mirror (r900797) + + Developer-visible changes: + * update ruby bindings test expectation (r880162) + * don't allow rangelist and mergeinfo API to modify input args (r879093) + + +Version 1.6.8 +(Not released, see changes for 1.6.9.) + + +Version 1.6.7 +(Not released, see changes for 1.6.9.) + + +[ Note: All revision numbers for versions prior to 1.6.7 reference the + original repository on svn.collab.net. For more information see: + http://svn.apache.org/repos/asf/subversion/README ] + + +Version 1.6.6 +(22 Oct 2009, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.6 + + User-visible changes: + * fix crash during 'svn update' (r39673) + * respect Apache's ServerSignature directive (r40008, -21, -31) + * don't add a file with mixed line endings, and then abort (issue #2713) + * support Neon 0.29. + * fix a crash in 'svn rm --force' (r37953) + * handle tree conflicts involving replacements (issue #3486) + * allow non-threadsafe sqlite if APR has no threads (r39301) + * print newline before plaintext SSL cert / password prompts (r38982, r39302) + * improve merge performance with implicit subtree mergeinfo (issue #3443) + * fix "libsvn_ra_svn/marshal.c assertion failed (opt || cstr)" (issue #3485) + * make file externals work for binary files (issue #3368) + * perform MIME type matching case-insensitively (issue #3479) + * do not treat non-existent revisions as HEAD in 'svn export' (issue #3400) + * revert r36720's default MIME type change back to "text/plain" (issue #3508) + * improve "tree conflict already exists" error message (r38872) + * fix failure to commit replacement of a directory (issue #3281) + * fix mod_dav_svn parent dir links to preserve peg revisions (issue #3425) + + Developer-visible changes: + * fix 2 failing tests in ruby bindings (r38886) + * do not require GNU grep for build (issue #3453) + * use '$SED' instead of 'sed' in build scripts (issue #3458) + * add svn.client.{log5,merge_peg3} to python bindings (r39635, -6, -7) + * include the time of a test run in tests.log (r39887) + + +Version 1.6.5 +(22 Aug 2009, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.5 + + User-visible changes: + * fix mod_dav_svn directory view links to preserve peg revisions (r38021) + * do not error on Windows when ALLUSERPROFILE dir nonexistent (r38053, -5, -7) + * properly escape lock comments over ra_neon (r38101, -2) + * allow syncing copies of '/' over ra_neon and ra_serf (issue #3438) + * make 'svnlook diff' show empty added or deleted files (r38458) + * fix building with Apache 2.4 (r36720) + * fix possible data loss on ext4 and GPFS filesystems (issue #3442) + * resolve symlinks when checking for ~/.subversion (r36023) + * don't let svn+ssh SIGKILL ssh processes (issue #2580) + * allow PLAIN and LOGIN mechanisms with SASL in svnserve (r38205) + * fix peg revision parsing in filenames like 'dir/@file.txt' (issue #3416) + * fix detection of Apache <2.0.56 (r38290, -3, -4) + * don't pretend to do tree conflict resolution (r38799, -801, -805) + * fix data corruption when syncing from svnserve to mod_dav_svn (r38686, -7) + * fix GNOME Keyring with '--non-interactive' option (r38222, -3, -61, -410) + * fixed: false "File '...' already exists" error during commit (issue #3119) + + Developer-visible changes: + * avoid referencing uninitialized variables (r38388) + * plug a couple of error leaks (r38572) + * improve windows test output (r38616, -7, -9, -49) + + +Version 1.6.4 +(06 Aug 2009, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.4 + + User-visible changes: + * fixed: heap overflow vulnerability on server and client + See CVE-2009-2411, and descriptive advisory at + http://subversion.apache.org/security/CVE-2009-2411-advisory.txt + + +Version 1.6.3 +(22 Jun 2009, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.3 + + User-visible changes: + * fix segfault in WC->URL copy (r37646, -56) + * let 'svnadmin load' tolerate mergeinfo with "\r\n" (r37768) + * make svnsync normalize svn:* props to LF line endings (issue #3404) + * better integration with external merge tools (r36178) + * return a friendly error message for 'svn diff' (r37735) + * update dsvn.el for 1.6 (r37774) + * don't allow setting of props on out-of-date dirs under neon (r37745) + * improve BASH completion (r36450, -52, -70, -79, -538) + * always show tree conflicts with 'svn st' (issue #3382) + * improve correctness of 'svn mergeinfo' (issue #3126) + * decrease the amount of memory needed for large commits (r37894, -6) + * work around an APR buffer overflow seen by svnsync (r37622) + * ra_svn clients now use TCP keep-alives if available (issue #3347) + * improve 'svn merge' perf by reducing server contact (r37491, -593, -618) + * stop propagating self-referential mergeinfo in reintegrate merges (r37931) + * fix NLS detection where -liconv is required for bindtextdomain() (r37827) + * don't delete unversioned files with 'rm --keep-local' (r38015, -17, -19) + * bump apr and apr-util versions included in deps to latest. (r37941) + * avoid temp file name collisions with ra_serf, ra_neon (r37972) + * fixed: potential segfault with noop file merges (r37779) + * fixed: incorrect output with 'svn blame -g' (r37719, -23, -41) + * fixed: bindings don't load FS libs when module search enabled (issue #3413) + * fixed: DAV RA layers not properly handling update/switch working copy + directory to revision/place in which it doesn't exist (issue #3414) + * fixed: potential abort() in the working copy library (r37857) + * fixed: memory leak in hash reading functions (r37868, -979) + + Developer-visible changes: + * improve memory usage in file-to-stringbuf APIs (r37907) + * reduce memory usage for temp string manipulation (r38010) + + +Version 1.6.2 +(11 May 2009, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.2 + + User-visible changes: + * vastly improve memory usage with 'svn merge' (issue #3393) + * make default depth for merge 'infinity' (r37156) + * make 'status --quiet' show tree conflicts (issue #3396) + * allow '--set-depth infinity' to expand shallow subtrees (r37169) + * return an error if attempting to reintegrate from/to the repo root (r37385) + * don't store bogus mergeinfo for '--ignore-ancestry', foreign merges (r37333) + * don't allow merge of difference between two repos (r37519) + * avoid potential segfault with subtree mergeinfo (r36613, -15, -31, -41) + * recommend sqlite 3.6.13 (r37245) + * avoid unnecessary server query for implicit mergeinfo (r36509) + * avoid unnecessary server query during reverse merges (r36527) + * set depth=infinity on 'svn add' items with restricted depth (r37607) + * fixed: commit log message template missing paths (issue #3399) + * fixed: segfault on merge with servers < 1.6 (r37363, -67, -68, -79) + * fixed: repeat merge failures with non-inheritable mergeinfo (issue #3392) + * fixed: another memory leak when performing mergeinfo-aware merges (r37398) + * fixed: incorrect mergeinfo on children of shallow merges (issue #3407) + * fixed: pool lifetime issues in the BDB backend (r37137) + + Developer-visible changes: + * don't fail if an embedding app has already initialized SQLite (issue #3387) + * resolve naming collisions with static stat() function in svnserve (r37527) + * fix an expectation for a failing dirent windows test (r37121) + + +Version 1.6.1 +(9 Apr 2009, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.1 + + User-visible changes: + * recommend Neon 0.28.4. (r36388) + * improve performance of 'svn merge --ignore-ancestry' (r36256) + * improve 'svn merge' performance with subtree mergeinfo (r36444) + * correctly proxy LOCK and UNLOCK requests (r36159) + * prevent a crash when updating old working copies (r36751) + * don't let svnmerge.py delete a nonexistent property (r36086, -767, -769) + * don't fail when upgrading pre-1.2 repositories (r36851, -7) + * allow escaping of separator characters in autoprops (r36763, -84) + * improve tempfile creation robustness on Windows (r36442, -3) + * fix change-svn-wc-format.py for 1.6.x working copies (r36874, -5) + * improve configure's detection of Berkeley DB (r36741, -2) + * don't allow foreign merges to add foreign mergeinfo (issue #3383) + * improve performance of 'svn update' on large files (r36389, et. al.) + * fixed: error leak and potential crash (r36860) + * fixed: parent directory handling on Windows (r36049, -50, -51, -131) + * fixed: unintialized memory errors (r36252, -3) + * fixed: potential working copy corruption (r36714) + * fixed: working copy upgrade error (r36302) + * fixed: pointer dereference error (r36783) + * fixed: error diff'ing large data with ignored whitespace (r36816) + * fixed: potential hang in ra_serf (r36913) + * fixed: problem with merge and non-inheritable mergeinfo (r36879) + * fixed: repeated merging of conflicted properties fails (issue #3250) + * fixed: excluding an absent directory segfaults (issue #3391) + + Developer-visible changes: + * ensure svn_subst_translate_cstring2() properly flushes data (r36747) + * make serf report a base checksum to apply_textdelta (r36890) + * syntax updates for strict C89 compilers (r36799) + * update RPM scripts for RHEL4 (r36834) + * allow tests to be run with Python 2.6.1 on Windows (r36149, -50, -51, -56) + * allow building JavaHL with Visual Studio 2008 (r36954) + * stop setting default translation domain in JavaHL (r36955) + * fixed: warning with Python 2.6 and ctypes bindings (r36559) + * fixed: undefined references to svn_fs_path_change2_create() (r36823) + + +Version 1.6.0 +(20 Mar 2009, from /branches/1.6.x) +http://svn.apache.org/repos/asf/subversion/tags/1.6.0 + + User-visible changes: + - General: + * Now require Windows 2000 or newer on Windows (r33170) + + - Major new features: + * identical files share storage space in repository (issue #2286) + * file-externals support for intra-repository files (issue #937) + * "tree" conflicts now handled more gracefully (issue #2282, #2908) + * repository root relative URL support on most commands (issue #3193) + + - Minor new features and improvements: + * pre-lock hook can now specify lock tokens via stdout (r32778) + * svnmucc: support '--with-revprop' (r29492) + * merge: log include-descendants in operational log (r30426, r30428) + * improved operational logging for 'svn switch' (r30517) + * new 'Header' keyword, similar to 'Id' but with full URL (r35386) + * warn/disallow when storing plain-text passwords (r31046) + * support KWallet and GNOME keyring for password storage (r31241, -337) + * client now caches SSL client cert passphrases (issue #2489) + * add '--prefix-file' option to 'svndumpfilter' (issue #2697) + * add '--ignore-externals' option to 'svn cp' (issue #3365) + * add '--with-no-revprops' to 'svn log' (issue #3286) + * new 'svnadmin pack' command to compress FSFS filesystems + * new SVNAllowBulkUpdates mod_dav_svn directive (issue #3121) + * new public mod_dav_svn URI syntax: path?[p=PEG][&r=REV] (r34076) + * new 'svnsync info' command to show synchronization information (r35053) + * conflict resolver supports display-conflict, mine-conflict and theirs-conflict + + - Client-side bugfixes: + * faulty reflexive merges (issue #2897) + * buffer overflow on a 0 byte string buffer (r35968, -74) + * conflict resolver needed more useful 'diff' option (issue #3048) + * disable username assumption (issue #2324) + * more accurate usage message for 'svn log' (r30449) + * do not repeat merge if target has explicit mergeinfo (issue #2821) + * corruption when filtering self-referential mergeinfo (r30467) + * filter empty mergeinfo with self-referential mergeinfo (r30510) + * pay attention to partial replay from the server in svnsync (r30440) + * improved property name handling in svnsync (r30480) + * properly recognize the file:/// in repository with svnsync (r30482) + * svn+ssh SIGKILLs ssh processes (issue #2580) + * 'svn up'/'svn co' early abort with svn:externals (issue #3148) + * improve tempfile names for conflict resolver (issue #3166) + * ra_serf: 'svn merge' aborts (issue #3212) + * 'svn cleanup' failed on non-ASCII characters (issue #3313) + * 'svn update' fails on moved, modified file with local mods (issue #3354) + * easier use of NTLM for proxy with ra_neon (r29874) + * 2-url merge from DAV-accessed foreign repo makes bad wcprops (issue #3118) + * can't add .svn (and children) to your wc via '--parents' (r35819) + * improved performance removing unversioned directories (r36111) + * 'svn cp --parents' had path URL encoding issues (issue #3374) + * support shell quoting rules in externals definitions (issue #2461) + * new SVN_LOCALE_DIR environment variable for localization (issue #2879) + * scheme and domain name in urls handled case insensitive (issue #2475) + * merge: pick default revisions with peg revision in single url (r30455) + * many other minor bugfixes, optimizations, plugs of memory leaks, etc + + - Server-side bugfixes: + * mod_dav_svn runs pre-revprop-change twice (issue #3085) + * mod_dav_svn ignores pre-revprop-change failure on delete (issue #3086) + * mod_dav_svn prevented lock breaks from being propagated to client (r29914) + * non-UTF8 filenames could enter repository (issue #2748) + * 'svnlook proplist' xml output (issue #2809) + * don't let mod_dav_svn hide errors from client (issue #3102) + * ra_serf failure during update (issue #3113) + * ra_serf comply with RFC 2617 in handling authentication headers (r35981) + * use both SHA1 and MD5 in the FS backends (r34388) + * many other minor bugfixes too numerous to list here + + - Contributed tools improvements and bugfixes: + * commit-email.pl: Deprecated; use mailer.py instead (r31755, -67) + * svnmerge.py migration tool munged svn:mergeinfo ordering (issue #3302) + * And other random sundry stuff + + Developer-visible changes: + - General: + * serf 0.3.0 required, when building with serf (r35586) + * require SQLite 3.4.0 or newer (r33520) + * allow the use of an in-tree SQLite amalgamation (r35263) + * svn_log_changed_path_t now includes a 'kind' field (issue #1967) + * BDB `changes' table inconsistency when APIs are misused (issue #3349) + * configure should prefer apr-1 over apr-0 if both are present (issue #2671) + * make 'Not Found' errors consistent between RA layers (issue #3137) + * fix a potential buffer overrun (r34374) + * many bug fixes and improvements to the test suite + + - API changes: + * notification system for properties and revision properties (issue #783) + * make ra_svn's merge commit-revprops public (r30462, r30453) + * mod_dav_svn operational logging compatible with svnserve logging (r30518) + * improve speed of svn_client__get_copy_source() (issue #3356) + * if fsfs commit fails return SVN_INVALID_REVNUM (r35950) + + - Bindings: + * new: ctypes python bindings + * many improvements to all bindings (Java, Perl, Python, and Ruby) + * respect CFLAGS in SWIG bindings (r35879) + * fix building Ruby bindings with Ruby 1.9 (r35852, r35883) + + +Version 1.5.9 +(06 Dec 2010, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.9 + + User-visible changes: + * fix proxying of LOCK and UNLOCK requests with WebDAV proxies (r36159) + * improve performance of --ignore-ancestry merges (r36256) + * avoid crash with when using subtree mergeinfo (r36613, -13, -31, -41) + * improve merge correctness with non-inheritable mergeinfo (r36789) + * fixed: repeated mergeinfo of conflicting properties fails (issue #3250) + * fix segfault in wc->URL copy (r37646, -56) + * make 'svn up --set-depth infinity' expand shallow subtrees (r37169) + * resolve symlinks when checking for ~/.subversion (r36023) + * make default depth of 'svn merge' infinity (r37156) + * don't allow foreign merges to add foreign mergeinfo (issue #3383) + * error if attempting to reintegrate to/from the repo root (r37385) + * let 'svnadmin load' tolerate mergeinfo with "\r\n" (r37768) + * improve memory performance in 'svn merge' (issue #3393) + * fixed: 'SVNPathAuthz short_circuit' unsolicited read access (issue #3695) + See CVE-2010-3315, and descriptive advisory at + http://subversion.apache.org/security/CVE-2010-3315-advisory.txt + * prevent crash in mod_dav_svn when using SVNParentPath (r1033166) + * limit memory fragmentation in svnserve (r1022675) + * fix server-side memory leaks triggered by 'blame -g' (r1032808) + * perform MIME type matching case-insensitively (issue #3479) + * respect Apache's ServerSignature directive (r880082) + * error early if attempting to use Serf >= 0.4.0 (r1041545) + + Developer-visible changes: + * fix pointer dereference (r36783) + * fix error leak (r36860) + * make basic_tests 12 compatible with Windows and Python 2.5+ (r35930) + + +Version 1.5.8 +(Not released, see changes for 1.5.9.) + + +Version 1.5.7 +(06 Aug 2009, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.7 + + User-visible changes: + * fixed: heap overflow vulnerability on server and client + See CVE-2009-2411, and descriptive advisory at + http://subversion.apache.org/security/CVE-2009-2411-advisory.txt + + +Version 1.5.6 +(26 Feb 2009, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.6 + + User-visible changes: + * allow colons within mergeinfo path names (r35040) + * make it impossible to add .svn to wc via 'svn add --parents' (r35143, -5) + * copy properties of added but uncommitted files (r32448) + * speedup JavaHL bindings on Windows (r35733) + * improve performance of log operation on < 1.5 servers (r35566) + * allow commits over Neon of files >2GB (POSIX only) (r34919, -24) + * allow serf from behind MS ISA proxy servers (r35981) + * prevent svnmerge-migrate-history.py from committing bogus mergeinfo (r35516) + + Developer-visible changes: + * fix error handling in mod_dav_svn (r35250, -86) + * support --server-minor-version in windows testsuite (r31393) + * fix depth_tests.py 23 on Windows with a BDB repo (r34875) + * allow svn_mergeinfo_parse() to tolerate unordered mergeinfo (r35297, -367) + * allow overlapping rangelists into svn_mergeinfo_parse() (r35466, -712, -713) + + +Version 1.5.5 +(22 Dec 2008, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.5 + + User-visible changes: + * allow prop commits on dirs with modified children (r34487, -92, -94) + * make Cyrus auth implementation always prefer EXTERNAL to ANONYMOUS (r33866) + * do not create mergeinfo for wc-wc moves or copies (r34184, -585) + * do not autoupgrade old BDB filesystems to 1.5 or 1.4 format (r34653, -6) + * return mergeinfo to prior state during reverse merges (r30257, r33024, -6) + * remove mergeinfo deleted by merge (issue #3323) + * make proxy slaves pass through txn GET and PROPFIND requests (issue #3275) + * merge can now use targets with inconsistent newlines (issue #3262) + * don't allow empty-string changelists (issue #3344) + * remove false positive ra_neon mergeinfo errors (r34822) + * improve performance of 'svn merge --reintegrate' (r34091, -4, and others) + * fixed: foreign merges keep UUID of foreign repository (r34050, -1, -3) + * fixed: properly encode diff headers used in conflict resolution (r34171) + * fixed: segfault in 'svn cp --parents' (r31311, -4) + * fixed: mergeinfo for '...' maps to empty revision range (issue #3312) + * fixed: segfault in BDB backend node-origins cache (r34506) + * fixed: broken merge if target's history includes resurrections (r34385, -93) + * fixed: invalid mergeinfo created on a subtree during merge (r34560, -2) + + Developer-visible changes: + * fixed: svn_repos_get_logs() chokes on some revision arguments (r33873, -4) + + +Version 1.5.4 +(24 Oct 2008, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.4 + + User-visible changes: + * Properly handle explicit mergeinfo added in merge source (r32968, -75) + * fixed: merging of paths containing spaces (r33641, -44) + * fixed: regression in mergeinfo-aware merges against 1.5.3 (r33693, -704) + + +Version 1.5.3 +(10 Oct 2008, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.3 + + User-visible changes: + * Allow switch to continue after deleting locally modified dirs (issue #2505) + * Update bash_completion to be compatible with 1.5 (r32900, -11, -12) + * Improve 'svn merge' execution time by 30% on Windows (r33447) + * Reuse network sessions during 'svn merge', improving performance (r33476) + * Improve temp file creation time on Windows (r33464) + * Greatly improve merge performance (r29969, r32463, r33013, -016, -022, -112) + * Improve file IO performance on Windows (r33178, -85) + * fixed: merging files with spaces in name (r33109, -121, -369) + * fixed: incorrect relative externals expansion (r33109, -121, -369) + * fixed: 'svn mv' hangs and consumes infinite memory (r33201, -12) + * fixed: correctness regression in 'svn log -g' (issue #3285) + * fixed: current early bailout of 'svn log -g' (r32977) + + Developer-visible changes: + * Allow the tests to run as non-administrator on Windows Vista (r31203) + * Allow out-of-tree build of bindings on BSD (r32409) + * Translate messages in svn_fs_util.h (r32771) + * fixed: bindings test for Perl 5.10 (r31546) + * fixed: building bindings and C API tests with VS2008 (r32012) + * fixed: svn_ra_replay API over ra_serf (r33173) + + +Version 1.5.2 +(30 Aug 2008, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.2 + + User-visible changes: + * Set correct permissions on created fsfs shards (r32355, -7) + * Pass client capabilities to start-commit hook (issue #3255) + * Disallow creating nested repositories (issue #3269) + * Support Neon 0.28.3 + * Properly canonicalize URIs with an empty hostname (issue #2116) + * Improved merge performance for superfluous ranges (r32643) + * Better error message for 'Malformed URL for repository' (r31867, r32365) + * Improved svn:externals parsing (r32672, -673, -674, -739) + * fixed: improper ordering in 'svnlook diff' output (r32019) + * fixed: mod_dav_svn memory leak with 'SVNPathAuthz short_circuit' (r32360) + * fixed: duplicate svn:externals targets fail on co/up (issue #3246) + * fixed: 'svn merge --depth' inconsistencies (issue #2825) + * fixed: ra_serf test failures (1.5.x-ra_serf-backports branch) + * fixed: memory leak and crashes in FS (r32545, -58, -82) + * fixed: core dump with relative externals (issue #3237) + * fixed: 'svn copy' working copy corruption (r32467, -70) + * fixed: perl bindings errors in non-English locale (issue #3258) + * fixed: 'svn merge' incorrectly reverses previous merges (r32494, -522, -523) + * fixed: 'svn merge' errors with subtree mergeinfo (issue #3067) + + Developer-visible changes: + * make libsvn_ra_neon initialization thread-safe (r32497, r32510) + * respect LDFLAGS in SWIG bindings (r32416, r32421, r32442) + * fixed: test failures in non-English locales (r32491) + + +Version 1.5.1 +(26 Jul 2008, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.1 + + User-visible changes: + * mergeinfo on switched subtrees should elide in repos (issue #3188) + * Add support for --config-dir to svnmerge.py (r31727) + * improve performance of bdb post-commit deltification (r31820, -59) + * return faster when there is nothing to be merged (r30748) + * don't commit an add of a missing item (issue #3198) + * don't create unneeded self-referential mergeinfo (issue #3157) + * support 'http-library' (if --enable-runtime-module-search) (r31425, -722) + * support Berkeley DB 4.7 (r32017, -29) + * fixed: make serf usable with root-level authz (r31464) + * fixed: 'svndumpfilter' partial-path matching bug (r31833) + * fixed: crash on invalid dates in 'log' and 'blame' (issue #2721) + * fixed: 'svn status --xml' outputting invalid XML (issue #2887) + * fixed: 'svn merge' prints incorrect range (r30746, -47) + * fixed: using neon/serf, can not replace branch (issue #2939) + * fixed: 'file not found' error when merging to a broken symlink (r31159, -79) + * fixed: using serf, crash or endless loop fetching authn data (r31619) + * fixed: ArrayIndexOutOfBoundsException in JavaHL bindings (r31719, -806) + * fixed: authn password lookup used wrong username (issue #2242) + * fixed: unbounded memory usage in wc-to-wc copy and move (r31868) + * fixed: subtree merges broken for non-intersecting ranges (issue #3199) + * fixed: invalid XML from 'svn log --xml' against pre-1.2 servers (r31875) + * fixed: 'svnlook diff' ignores --diff-copy-from for properties (issue #3248) + * fixed: 'svnlook diff' doesn't report that binary files differ (issue #3249) + * fixed: bogus results from commits to subtrees added by merge (issue #3240) + * fixed: non-existent subtree in destination breaks the merge (issue #3067) + * fixed: serf merge bug too complex to describe here (r32056) + * fixed: 'svn log -g' correctness and speed (issue #3220, issue #3235) + * fixed: merge chokes on renamed subtrees (issue #3174) + + Developer-visible changes: + * export svn_path_is_url() to the bindings (r31603) + * don't clobber LDFLAGS in configure when given '--with-zlib' (r31825) + * make libsvn_ra depend on libsvn_delta unconditionally (r31852) + * correctly set the peg revision for copy in JavaHL (r31994) + * 'svn mergeinfo' handles wc paths (r31023, -873, -874, -929, -930, -038) + * fixed: crash when when svn_ra_open3() is passed a bogus URL (r31223) + * fixed: JavaHL compilation on Windows (r31737) + * fixed: crash in calling apr_pstrcat (affects TortoiseSVN) (r32080) + + +Version 1.5.0 +(19 Jun 2008, from /branches/1.5.x) +http://svn.apache.org/repos/asf/subversion/tags/1.5.0 + + User-visible changes: + - Major new features: + * Merge Tracking [foundational] (issue #820) + * Sparse checkouts (see new '--depth' option) (issue #695) + * Interactive conflict resolution (r25670 et al) + * svn:externals handles relative URLs (issue #1336) and peg URLs + * Changelist support + * WebDAV transparent write-through proxy + * Better support for large FSFS deployments (via sharding & partitioning) + * Cyrus SASL support for ra_svn and svnserve (issue #1144) + + - Minor new features and improvements: + * 'svn resolve' (with '--accept' option) replaces "resolved" (issue #2784) + * 'svn move file1 file2 ... dir' now moves the files into dir (issue #747) + * 'svn mkdir' and 'svn copy' now take '--parents' option (issue #1776) + * 'svn delete' now takes '--keep-local' to not remove working copy files + * 'svn copy', 'move' now support peg revisions (issue #2546; also r26484) + * 'svn copy A B ; svn move B C' now the same as 'svn copy A C' (issue #756) + * 'svn copy -rBASE' now works in a working copy (issue #1643) + * 'svn import' now takes '--force' (issue #2806) + * 'svn status -u' now shows of locally deleted directories (issue #2420) + * 'svn switch' now takes '--force' (issue #2392) + * 'svn switch' now takes '--ignore-externals' option (issue #2189) + * 'svn switch' now supports peg revisions (issue #2545) + * 'svn checkout' now takes '--force' option (issue #1328) + * 'svn proplist' and 'svn propget' now support peg revisions (issue #3070) + * 'svn propget' now takes '--xml' option (issue #2696) + * 'svn propedit' now support URLs (issue #2238, but see issue #2923) + * 'svn proplist --quiet' no longer prints extra info (issue #1547) + * 'svn diff --summarize' now takes '--xml' option (issue #2967) + * 'svn diff -x' now takes '-p' extension option (issue #2995) + * 'svn log' now takes '-c' option (r27933) + * 'svn log' now takes '-l' as short form of '--limit' (r25829) + * 'svn log --xml' now takes '--with-revprop' option (issue #2850) + * 'svn diff'/'svnlook diff' now show property actions better (issue #3019) + * 'svn merge' now has informative messages on reverse merges (issue #2848) + * 'svn merge FILE' now honors '--ignore-ancestry' (issue #2853, r25891) + * 'svn merge' handles multiple notifications for single items (issue #2828) + * 'svn merge' handles skipped path better (issue #2829) + * 'svn merge' handles merges from foreign repositories more completely + * 'update', 'checkout', 'switch' now handle obstructions gracefully (r22257) + * 'svn update' now takes '--force' (issue #2392) + * 'svn update' now sometimes copies or moves local files, for efficiency + * 'svnadmin lslocks' now accepts path within repository (issue #2965) + * 'svnadmin recover' now supports FSFS repositories (issue #2992) + * 'svnadmin verify' now has '-q' and '-r' options (r22103) + * 'svnadmin setrevprop' command added (r21736) + * 'svnadmin setuuid' command added (r28511) + * 'svnsync sync' now shows commit progress like 'svn commit' + * 'svnsync' now takes '-q, --quiet' option (r26465) + * 'svnsync' now supports separate authn for source/target (issue #2717) + * 'svnsync copy-revprops' now supports revision ranges (r23498) + * 'svnsync copy-revprops' now supports "HEAD" revision alias (r23500) + * 'svnmucc' is new name for contrib tool formerly called 'mucc' + * 'svnmucc' now has propset and propdel subcommands (issue #2758) + * 'svnmucc' now has more authentication options + * 'svnmucc' now now takes '--non-interactive' option (r25977) + * 'svnmucc' now takes a global base revision, for extra safety (r23764) + * 'svnlook' now takes '--extensions' option (issue #2912) + * 'svnlook' now takes '-N' option (issue #2663) + * 'svnlook history' now takes '-l' / '--limit' option (r25843) + * 'svnserve' now takes '--config-file' option (r24119) + * 'mod_dav_svn' now uses Apache default mime-type for files (issue #2304) + * new '--with-revprop' option on all commands that commit (issue #1976) + * now accept "peg dates" (URL@{DATE}), behaving like peg revs (issue #2602) + * easier to try out experimental ra_serf http:// access module + * select ra_neon vs ra_serf on a site-by-site basis in config (r25535) + * client-side post-commit processing now more efficient (issue #2607) + * windows binaries now use a custom crash handler (issue #1628) + * add vim swap file patterns to default global-ignores (r24348) + * add "*.pyc" and "*.pyo" patterns to default global-ignores (issue #2415) + * add unix/libtool library patterns to default global-ignores (issue #2415) + * naming scheme for conflict files is now configurable (issue #2474) + * removed svn-ref.tex as it's extremely out of date (issue #2762) + * improved cancellation response in many situations + * support Neon up to 0.28 + * character set conversion now uses native API on Windows (r25650) + * HTTP authn protocol now configurable (for Neon 0.26 and higher) (r21531) + * http:// (over Neon) supports HTTP redirection / relocation (issue #660) + * support PKCS#11-provided (smartcard) SSL client certs with Neon (r29421) + * authz now supports aliases (r21982) + * authz token rules for authenticated-only, anonymous, and inverse (r23750) + * mailer.py now supports properties in commit messages (r21684) + * ra_serf now supports NTLM/SSPI authentication (issue #2900) + * warn if try to turn off boolean property via propset/propedit (r25486) + * display repository basename in XML and HTML index views (r25837, r25838) + * config 'http-auth-type' can be overridden to force BASIC auth (r23900) + * translation updates for all languages, as usual + * Revamp mod_dav_svn logging; see tools/server-side/svn_dav_log_parse.py + * misleading configure arg --enable-dso now --enable-runtime-module-search + + - Client-side bugfixes: + * 'svn revert' of missing scheduled addition broke wc (issue #2425) + * 'svn export' should export svn:externals from local copies (issue #2429) + * 'svn status -uN' should show status of files (issue #2468) + * 'svn update' overwrote if local timestamp unchanged (issue #2746) + * 'svn update -N' errored when receiving a deletion (issue #3039) + * 'svn merge' would delete locally modified props (issue #2857) + * 'svn log --xml' could output invalid XML (issue #2866) + * 'svn copy' on URL with spaces made wrong WC file name (issue #2955) + * 'svn diff' was failing w/ large diffs on Windows (issue #1789) + * 'svn delete' no longer deletes locally-modified files (issue #1808) + * 'svn move' moved files to wrong directory on Windows (issue #1869) + * 'svn revert' mistakenly used leftover .svn-revert files (issue #2927) + * 'svn diff' output now shows relative paths (issue #2723) + * 'svn diff' wasn't ignoring all EOLs (issue #2920) + * 'svn cleanup' no longer fails on a missing .svn/tmp dir (r23370) + * infinite loop in UTF conversion in non-C locale (issue #2577) + * interrupting "svn status" could make svn crash (issue #2623) + * commit-email.pl date header output now RFC2822-compliant (issue #2633) + * authz write access to folder wasn't permitting locking (issue #2700) + * stop complaining just because $HOME is unreadable (issue #2363) + * do not display unescaped characters in error message (issue #2471) + * propchange received on subdir merge causes conflict (issue #2969) + * revert replaced-with-history files should restore checksum (issue #2928) + * catch improper arguments to diff (issue #2996) + * handle URLs like http://hostname (i.e. no path part) (issue #1851) + * config autoprops honored regardless of case of entry (issue #2036) + * "Cannot replace a directory from within" error now rarer (issue #2047) + * handle _svn/.svn as part of a path (issue #3026) + * make permissions changes on symlinks a no-op (issue #2581) + * error usefully if asked to update a URL (r22296) + * fixed infinite loop on Windows if fail to find repository root (r22483) + * 'svn info $REPO_ROOT' now supports pre-1.2 svn:// servers (r26264) + * be more resilient in the face of faulty .svn/entries files (r26482) + * 'svn diff -x --ignore-eol-style' failed to ignore all EOLs (r27094) + * rare property dataloss bug now fixed (issue #2986, see also r29538) + * fixed faulty status reporting for some missing directories (issue #2804) + * 'svn diff --summarize' showed wrong output paths (issue #2765) + * propset and move interaction could cause property weirdness (r25833) + * 'svn propget .@HEAD' now works (issue #3012) + * 'svnsync' had bug with replaced+modified rev over serf (issue #2904) + * 'svnsync --config-dir' sometimes ignored, thus tunnel agent bug (r27056) + * update/merge safely receives file on top of schedule-add file (r23506) + * http:// (over Neon) reports progress while disk-spooling delta (r26271) + * print "Out of memory" before dying from memory shortage (issue #2167) + * warn when used on old checkout without a repository root entry (r25168) + * merge to missing file target wrongly appeared to succeed (issue #2782) + * 'svn merge URL PATH -cX' could cause property corruption (issue #2781) + * URL parsing now consistently checks for error earlier (issue #2207) + * security hole: files could be created above cwd (r26047, CVE-2007-3846) + * local property mods to replaced-with-history file could be lost (r26364) + * revert of replaced-with-history path left copyfrom info (r23452) + * character encoding translation could hang (r23492) + * un-substituting keywords was buggy ($Id$ vs. $Id:$) (issue #2640) + * ra_neon and ra_serf lost pre-revprop-change hook output (issue #443) + * merge of non-empty subdir could be committed incorrectly (issue #1962) + * many other minor bugfixes, optimizations, plugs of memory leaks, etc + + - Server-side bugfixes: + * segfault in svnserve and svnversion commands fixed (issue #2757) + * segfault when stopping httpd (if BDB repository) fixed (issue #2732) + * 'svnadmin dump' had a path ordering bug (issue #2641) + * better FSFS support for NFS v3 and lower (r24470) + * better FSFS support for some buggy NFS clients (r29448) + * authentication and authz bugs w.r.t. anonymous access (issue #2712) + * inconclusive authz result should deny, not allow (r23815) + * better reporting of problems parsing authz files (r22329) + * set svn:date revprop even if dumpstream does not (issue #2729) + * http:// commit can now create empty files properly (r25471, r25474) + * squelch not-a-directory errors in both FS backends (issue #2549) + * segfault on update-report response without base revision (issue #3023) + * 'svnserve --root PATH' checks that PATH exists (r22580, r22701) + * 'svnlook propget -t TXN_NAME' reports errors better (r22772) + * make location of mod_dav_svn activity database configurable (r24873) + * select only paths that are proper children of requested path (r25231) + * http:// commit error could leave empty transactions behind (r23594) + * 'svn switch --relocate' now works against unreadable repos root (r23848) + * many other minor bugfixes too numerous to list here + + - Contributed tools improvements and bugfixes: + * svn_load_dirs.pl: + - Support global-ignores list (issue #2470) + - Allow "@" in filenames (r22203, Debian bug 359145) + - Add -no_auto_exe option (r26399) + * svnmerge.py: + - fixed: Always get end_rev from source instead of target (issue #2863) + - fixed: 'init' now chooses a better default revision range (issue #2810) + - fixed: Consider revs changing blocking status as reflected (issue #2814) + - Performance inmprovement (issue #2812) + - initialized revisions can be excluded (issue #2851) + * new 'svn-populate-node-origins-index' tool (issue #3024) + * new 'svn-merge-vendor.py' to assist in merging vendor branches (r23030) + * 'svn2rss.py' is now called 'svn2feed.py' + * svn2cl: New release 0.9 (r24498) + * commit-email.pl: various improvements (r22971, r22589) + * commit-email.rb: various improvements + * psvn.el: too many improvements and new features to list them all here + * dsvn.el: improve XEmacs compatibility (r24337) + * svn-tweak-author.py: make NEWAUTHOR argument optional (r24387) + * And more stuff that we just didn't have time to list. Enjoy. + + Developer-visible changes: + * General: + - libsvn_ra_neon is new name for libsvn_ra_dav (to accommodate ra_serf) + - many abort() calls removed, replaced with error returns + - client and server now do capabilities exchange (r29358 et al) + - gen_win.py: auto-detect the path to the JDK on Windows (r24333) + * API changes: + - many, many new APIs and types as part of the new features in 1.5.0 + - APIs to allow retrieving multiple revprops in one fetch (issue #2850) + - basic progress reporting for ra_svn (issue #901) + - new APIs for creating and using iterators (r26533) + - svn_fs_node_origin_rev finds line of history origin (issue #3017, #3024) + - svn_revnum_parse for parsing revision numbers (r26195) + - svn_path_is_canonical for validating paths (r26481) + - new API svn_fs_txn_root_base_revision() (r22610) + - pass individual arguments rather than config objects (r25182, r25190) + - clients can now extend HTTP User-Agent header (r28613) + - SVN_ERR_RA_DAV_PATH_NOT_FOUND is deprecated and no longer raised + * Bindings: + - Many improvements to all bindings (Java, Perl, Python, and Ruby) + + +Version 1.4.6 +(21 Dec 2007, from /branches/1.4.x) +http://svn.apache.org/repos/asf/subversion/tags/1.4.6 + + User-visible changes: + - Client: + * fixed: unbounded memory use in "svn cat" over ra_svn (r26964, -8) + * fixed: 'svn diff --summarize file' displays erroneous output (issue #2765) + * fixed: 'svn status' wrong on previously-reverted deleted dir (issue #2804) + * fixed: 'svn up' can delete unversioned symlinks (issue #1808) + * fixed: use correct properties for locally replaced files (issue #2743) + * fixed: 'svn info -R $REPO_ROOT' w/ pre-1.2 svnserve broken (r26264) + * fixed: svnsync ignores '--config-dir' (r27056) + * datestamps can be localized (r26156) + * fixed: text base not updated when merging a replaced file (issue #2698) + * fixed: inverted 'switch --relocate' error message (r22355) + * fixed: sporadically failing file and directory removal on Windows (r25520) + * fixed: property file handling for schedule-delete files (r25833) + * fixed: allow invalid svn:eol-style values (r28331) + * fixed: 'svnadmin rmlocks' should error when no path provided (r28431) + * support neon 0.26.4 (r26077) + + - Server: + * fixed: authz granted if calculation inconclusive (r23815) + * fixed: svndumpfilter crashes on Windows (r23494) + * fixed: wrong pointer type used for memset (r27263) + * fixed: invalid FSFS directory cache can corrupt repository (r27256) + * fixed: dir props on FSFS filesystem root never conflict (issue #2608) + + - Client and Server: + * fixed: "No newline at end of file" message translated (issue #2906) + * use compressed delta encoding for 'svn blame' in svnserve (r26115) + * translation updates for Simplified Chinese + + Developer-visible changes: + * svnserveautocheck.sh script is executable (r23942) + * add RHEL5 RPM (r25593) + * test suite passes with trunk servers (forwards-compatibility) (r25607) + * javahl bindings: + - improve error reporting from native code (r25208) + + +Version 1.4.5 +(27 Aug 2007, from /branches/1.4.5) +http://svn.apache.org/repos/asf/subversion/tags/1.4.5 + + User-visible changes: + * fixed: file placement vulnerability (Win32 clients only) + See CVE-2007-3846, and descriptive advisory at + http://subversion.apache.org/security/CVE-2007-3846-advisory.txt + + +Version 1.4.4 +(30 May 2007, from /branches/1.4.x) +http://svn.apache.org/repos/asf/subversion/tags/1.4.4 + + User-visible changes: + - Client: + * fixed: 'svn up' of replaced file without history fails (issue #2618) + * fixed: 'svn export' succeeds on non-existent URL (r23191, -3, -5, -200) + * fixed: 'svn diff' fails writing large hunks to Win console (issue #1789) + * fixed: 'svn merge' shows 'G' notifications for unchanged files (r24483) + * fixed: svnsync cannot sync unreadable modified dir copies (issue #2705) + * fixed: ra_dav litters empty transactions if initial setup fails (r23594) + * fixed: inconsistent expansion of revision number keywords (issue #1743) + + - Server: + * fixed: rare dirprop dataloss leading to BDB repo corruption (issue #2751) + * fixed: race condition when changing FSFS revprops (r23439, r23440) + * fixed: 'svnadmin load' invents svn:date if none exists (issue #2729) + * fixed: svnserve can't commit locked file if root unwritable (issue #2700) + * fixed: 'svnadmin dump' output invalid for non-ASCII paths (issue #2641) + * fixed: security flaw in 'svn prop*' commands [CVE-2007-2448] + (r25095, -099, -104, -105, -10) + + - Client and Server: + * fixed: hang during character translation (r23491, r23492) + * translation updates for Simplified Chinese, Japanese, and Norwegian + + Developer-visible changes: + * new "make svnserveautocheck" testing target (r23558) + * fixed: ra_serf fails checkout if access to repos root is forbidden (r23846) + * fixed: svn_client_cat2() doesn't accept WORKING as a revision (r23556) + * javahl bindings: + - fixed: potential segfault in initialisation (r23383) + - fixed: SVNClientSynchronized.logMessages() isn't synchronised (r23978) + - fixed: SVNClient.info2() misreports itself as unlock in errors (r24219) + * SWIG/perl bindings: + - fixed: ra_do_{update,switch,status} don't work with Perl delta editors + (r20667, r22311) + * SWIG/python bindings: + - fixed: memory leak whenever C APIs returned errors (r23521) + * SWIG/ruby bindings: + - fixed: typos in method Svn::Wc#merge_prop_diffs and docs (r23405, -6) + + +Version 1.4.3 +(18 January 2007, from /branches/1.4.x) +http://svn.apache.org/repos/asf/subversion/tags/1.4.3 + + User-visible changes: + - Client: + * fixed: crash using automatic auth protocols with Neon 0.26 (r22440, -61) + * fixed: svn_load_dirs.pl cannot import file names containing '@' (r22203) + * fixed: error when committing replaced directories (r22991, -8) + * fixed: inability to change file perms due to existing file perms (r23018) + * include newest version of svn-graph.pl (r22969) + + - Server: + * fixed: incorrectly reporting authz circular dependencies (issue #2684) + * fixed: potential filesystem memory leak in commit finalisation (r22729) + + - Client and Server: + * fixed: crash in character translation, particularly on Windows (r22417) + * fixed: potential string corruption when resizing string buffers (r22689) + * translation updates for Korean, Spanish, Italian, Simplified Chinese, + and Japanese (fixing issues #2649 and #2681) + + Developer-visible changes: + * support Neon 0.26.2 (issue #2666) + * update (experimental) ra_serf repository access module for DAV (r22872) + * Windows installer improvements (r21516, r22155, r22224) + * fixed: svn_{ra,repos}_replay() doesn't send checksums (r22346, -51, -52) + * fixed: error when calling svn_repos_replay2() with a txn root (r22609) + * fixed: Solaris packaging script broken (issue #2669) + * javahl bindings: + - fixed: auth cache is created in the current directory (r22780) + - fixed: SVNAdmin's setLog() method always fails (r22387) + - fixed: target dependency order in generated build scripts (r22209) + * SWIG/perl bindings: + - fixed: memory leak when calling methods on a Perl commit editor (r22332) + + +Version 1.4.2 +(2 November 2006, from /branches/1.4.x) +http://svn.apache.org/repos/asf/subversion/tags/1.4.2 + + User-visible changes: + - Client: + * new "notes/svnsync.txt" file explains common svnsync usage + * install a manpage for svnsync (r21403) + * install/package svnsync on Windows (r21387, r21424) + * translation updates for all languages + * dramatically speed up commit of wc-to-wc copy (r21471) + * fixed: support 'svn co URL@{DATE}' (issue #2602) + * fixed: cannot access repositories with spaces via svn:// (issue #2612) + * fixed: passing full URL in some DAV requests, breaking proxies (r21526) + * fixed: history-tracing can fail for renamed directories (issue #2600) + * fixed: crash if interrupted while opening a working copy (r21792) + * fixed: 'svn merge' should notify about conflicted files (issue #2584) + * fixed: 'svn revert' should notify about prop-only reverts (issue #2517) + * fixed: 'svn status -u' not showing props changed on wc root (issue #2533) + * fixed: 'svn status -u' fails in a read-only working copy (r21904, -19) + * fixed: 'svn up' failing with checksum mismatch error (issue #2618) + * fixed: 'svnsync sync' copying missing implicit revprops (issue #2613) + * fixed: svnsync unable to synchronise copies of URL-unsafe paths (r22092) + * svnshell tool: support "setrev head" (r20992) + * include newest version of svnmerge.py + + - Server: + * FSFS: improve detection of disk write errors (r21346) + * FSFS: prevent API violation from corrupting repository (issue #2467) + * improved error checking when running hook scripts, etc (r21483) + * mailer.py: new commit_url option links to web page for a commit (r21333) + + Developer-visible changes: + * support Neon 0.26.0 and 0.26.1 (r21289, r21293, r21956) + * support current CVS versions of libtool (post-1.5.22) (r22120) + * now compiles on architectures without APR_HAS_DSO (e.g. RISC OS) (r21473) + * fixed: build error on FreeBSD due to missing svnsync manpage (r21403) + * RHEL3 RPM package requires correct version of Apache httpd (r21974) + * numerous improvements to coverage of the test suite + * javahl bindings: + - compile Java bytecode for Java 1.2 VM (r21765, -7, r21814) + - fixed: crash if using 1.4.x bindings with older libraries (r21316, -429) + - fixed: crash when empty destination path passed to checkout (r21770) + * SWIG/ruby bindings: + - fixed: accept nil for Svn::Repos#load_fs's parent_dir argument (r21793) + * SWIG/python bindings: + - fixed: crash when using an apr_hash_t typemap (issue #2606) + - fixed: in tests, use URLs that work on Windows (r21392) + * SWIG/perl bindings: + - fixed: ra_replay works with Perl delta editors (r20666) + + +Version 1.4.1 +(Not released, see changes for 1.4.2.) + + +Version 1.4.0 +(10 September 2006, from /branches/1.4.x) +http://svn.apache.org/repos/asf/subversion/tags/1.4.0 + + User-visible changes: + - Client: + * new 'svnsync' commandline tool for repository replication + * numerous working copy improvements (WARNING! upgrades to new format!): + - improved performance when detecting modified files (r18628 -56) + - new property storage is faster and uses less disk space (r17583) + - internal wcprops take up less space (r19433 -37) + - large file commit speedups (r17861 -73 18867 -918 -29 -44 -45 -48 -49) + - reduce memory usage for large working copies (r19183 -538) + - increased working copy stability with merge, copy and move: + (fixes issues #845, #1516, #1553, #2135, #2144, #2148) + * new switches added: + - 'svn blame --force' (issue #2509) + - 'svn diff/merge -c/--change' (r17054 -6 -68 18568 -741) + - 'svn diff --summarize' (issue #2015) + - 'svn merge/blame -x' (r18716 -20) (r18602 -857) + * 'svn log' now supports peg revisions (issue #2287) + * 'svn export' now creates intermediate directories if needed (r20030) + * use switch/relocate when svn:externals updated (issue #2209) + * internal diff can ignore whitespace and eol style changes (issue #2121) + * conflict markers now match the file's eol style (issue #1325) + * new svn2cl, svn-viewdiff and svn-resolve contrib scripts + * numerous improvements to svnmerge.py, vc-svn and psvn + * translation updates for all languages + * Mac OS X: store cached passwords encrypted in Keychain (r17619 -43) + * fixed: 'svn ls' slow over ra_dav (issue #2151) + * fixed: 'svn import' not handling eol-style correctly (issue #2433) + * fixed: 'svn blame' should default operative rev range to peg rev (r18400) + * fixed: 'svn blame' ignores eol-style (issue #2431) + * fixed: 'svn checkout' should default operative rev to peg rev (r18422) + * fixed: 'svn diff' supports all eol styles (r17624 -8 -61 18195 -392) + * fixed: 'svn diff' multi-target memory leak (r17518) + * fixed: 'svn merge' showing wrong status with external diff3 (issue #1914) + * fixed: 'svn merge' not merging added dir into deleted dir (issue #2515) + * fixed: 'svn rm' of non-existent item should fail (issue #2440) + * fixed: 'svn status' should skip unversioned files (issue #2030) + * fixed: 'svn status' shows added and conflicted files as added (r20382) + * fixed: 'svn switch --relocate' may set wrong repos root (r17031) + * fixed: 'svn switch --relocate' memory leak (r19535) + * fixed: 'svn switch --relocate' not caching passwords (issue #2360) + * fixed: 'svn info' not showing locks sometimes (r19777) + * fixed: incorrect merge of add of binary file already in WC (issue #2403) + * fixed: possible dataloss if editing immediately after merge (r20609 -12) + * fixed: lots of diff wc<->repos bugs + * fixed: unfriendly error message on propget on nonexistent path (r19399) + * fixed: spurious revert report after manual conflict removal (issue #2517) + * fixed: don't allow -rPREV on schedule add path (issue #2315) + * fixed: keywords with dollar signs cause badness (issue #1780) + * fixed: really revert file with locally modified keywords (issue #1663) + * fixed: deleting schedule add file leaves working props file (issue #2419) + * fixed: svn:needs-lock and read-only-ness not always in sync (issue #2306) + * fixed: post-commit error output not sent to the client (issue #443) + * fixed: not locked error on commit of switched path (issue #2353) + * fixed: svn_apply_autoprops.py should trim whitespace from props (r20790) + * fixed: show locking notifications in local path style (r20927) + * fixed: encoding error on error messages from invalid options (r20883) + + - Server: + * support for new 'svnsync' repository mirroring utility + * support for BDB 4.4, including automatic recovery (issue #2449) + * new contrib hook scripts: + - enforcer + - detect-merge-conflict.sh + - case-insensitive.py + * new tools script svn-backup-dumps.py + * new tools hook script log-police.py + * svnserve improvements: + - can now run as a native Windows service (r18855) + - new option --pid-file (r17836) + - allow the password database to be read-only (r16840) + * mod_dav_svn improvements: + - fixed: error conversion crash (r19516) + - fixed: unfriendly error when locking already locked path (issue #2275) + - fixed: xml escaping bugs (r19760 -85 -86) + * authorization improvements: + - new mod_dontdothat apache module (r19531) + - new mod_authz_svn directive AuthzSVNNoAuthWhenAnonymousAllowed (r18680) + - error out when authz rules contain unexpected characters (r19471) + * support .wsf hook scripts on Windows (r18972, 19076) + * lots of improvements to mailer.py and commit-email.pl + * FSFS back-end performance improvements (r17125 19119 -456 -58 -59) + * fixed: 'svnadmin verify' output not in native encoding (issue #1997) + * fixed: uuid file in FSFS could be destroyed on write error (issue #2193) + * fixed: FSFS path encoding bug (r17774) + * fixed: don't crash on corrupt repositories (r17625) + * fixed: expect error output from hook scripts in native encoding (r17101) + * fixed: catch errors starting hook scripts (r16891 17041 -81) + * fixed: svnserve and authz can cause broken WCs (issue #2566) + * fixed: the default hook script templates should be vanilla sh (r20796) + + - Both: + * delta compression improvements: + - new delta encoding reduces size (r18363 -94 -66 -78 -98 -99 -457 -950) + - xdelta algorithm speed improvements (r18986, 19047) + * don't bail on invalid locale (r19445) + * improve speed of non-verbose svn ls (r17067 -71) + * fixed: delta combiner reading past EOF (r17743) + + Developer-visible changes: + * require APR >= 0.9.7 to improve error detection for FSFS repos (r19915) + * require zlib, for svndiff1 delta encoding (r18363) + * support SWIG 1.3.29 (r19968) + * support autoconf 2.60-dev (r19919 20632 -36) + * removed no-longer-supported Red Hat 7.x RPMs (r20462) + * add support for building RPMs for x86-64 architecture (r20548 -552) + * numerous improvements to gen-make.py build system, especially on win32 + * removed Visual Studio.NET APR 0.9 project files (r20170) + * numerous improvements to the test suite + * new public APIs: + - keyword / eol translation helpers and generic streams (see svn_subst.h) + - new generic stream helpers (see svn_io.h) + - authn providers made available to other clients (see svn_auth.h) + - svn_cmdline_setup_auth_baton + - svn_dso_initialize, svn_dso_load + - svn_client_diff_summarize and svn_client_diff_summarize_peg + - svn_client_list + - svn_config_has_section + - svn_txdelta_compose_windows and svn_txdelta_apply_instructions + - svn_txdelta_stream_create + - svn_diff_file_options_create and svn_diff_file_options_parse + - svn_err_best_message + - svn_compat_wrap_commit_callback + - svn_uuid_generate + - svn_user_get_name and svn_user_get_homedir + - svn_io_get_dir_filenames + - svn_ra_reparent + - svn_ra_replay + - svn_wc_revision_status + - several rev'd APIs, see doxygen docs + * flush stdout after each status/notification line (r19476 -656) + * new (experimental) ra_serf repository access module for pipelined DAV + * .svn/entries use a less verbose non-xml format (r19420) + * make recursive 'svn ls' streamy (issue #1809) + * remove svn-config script + * empty-file and README.txt removed from WC admin areas (r17181 -268 -364) + * replace cmdline client XML DTDs with RNG schemas (r16379 -80 -93 -571 17248) + * fixed: log --limit against old svnserve leaves unusable session (r19638) + * fixed: Solaris build problems (r19636) + * fixed: blame of WORKING revision shouldn't give BASE (r19558) + * fixed: svn_client_copy and _move should fail if target exists (issue #2188) + * fixed: svn_io_file_rename and readonlyness on Windows and UNIX (r17366 -69) + * fixed: ra_dav memory leak when reusing session (issue #2247) + * fixed: console character encoding problems when built with VS2005 (r20108) + * fixed: various problems with --enable-dso and global pools (r20996, r20999) + * fixed: installer file syntax error in new versions of Inno Setup (r21022) + * SWIG bindings: + - SWIG/python bindings: + - new support for svn_client_info (r19413) + - SWIG/ruby bindings: + - full support for Subversion 1.4 APIs, including : + svn_ra_replay and svn_diff_summarize + - numerous bug fixes + - add ruby documentation (make install-swig-rb-doc) (r20166) + - add APIs for adding a provider (r21079) + - SWIG/perl bindings: + - new support for svn_client_info (r18758) + - minor corrections to SVN::Fs (r19312) + * javahl bindings: + - APIs to get version info for the native libraries (r17604 -07) + - API for path validation (r18989, r19079) + - C++/Java code refactoring, cleanup, and consolidation + - fixed: handle possible errors from date/time conversions (r17213) + - fixed: SVNClient username/password JVM crash on null input (r19803 -13) + - fixed: specify default UUID load action (r18030) + - fixed: compile error on Visual Studio 2005 (r18054) + + +Version 1.3.2 +(23 May 2006, from /branches/1.3.x) +http://svn.apache.org/repos/asf/subversion/tags/1.3.2 + + User-visible changes: + - Client: + * fixed: 'svn st -u' crash on missing subdirs (r19348, -71, issue #2551) + * fixed: leaving stray working copy locks on cancellation (r18893) + * fixed: svn_load_dirs.pl trying to import .svn and _svn dirs (r18549) + * svn_load_dirs.pl symlink support (issue #2478) + * translation updates to Japanese, Traditional Chinese. + + - Server: + * fixed: mod_dav_svn memory leak when listing large dirs (r19528) + * fixed: mod_dav_svn crash on valid request (r19520) + * fixed: svnserve protocol error in lock, causing client hang (issue #2548) + * mailer.py: add Content-Transfer-Encoding header (r19319) + * mailer.py: fixed: named substitutions incorrectly ignored (r18114, -681) + * fixed: authz requires read access for root for writes (issue #2486) + * svnauthz-validate: add config file validation tool (r18504, -09) + + Developer-visible changes: + * fixed: tests don't catch repository creation failure properly (r19149,-51) + * support SWIG 1.3.28 + * support APR 0.9.x >= 0.9.10 (r19039, -57, -60) + * python bindings: + - fixed: link error on OpenBSD (r18983) + * ruby bindings: + - fixed: memory leak (r19493) + - fixed: NULL argument conversion bug (r19543) + + +Version 1.3.1 +(25 March 2006, from /branches/1.3.x) +http://svn.apache.org/repos/asf/subversion/tags/1.3.1 + + User-visible changes: + - Client: + * fixed: segfault moving unversioned files (issue #2436) + * fixed: verbose list broken over ra_dav (issue #2442) + * fixed: 'svn ci -m path_name' not requiring '--force-log' (r17956) + * fixed: crash on mixed-case https URL scheme (r18042) + * fixed: crash in status with ignored directories (r18291) + * fixed: strip peg rev from default checkout directory (r18416) + * fixed: diff crash with non-recursive checkout (r17231, 18539, -41) + * fixed: 'svn ls' URL encoding bug with locks (r18665, -68) + * fixed: unlock circumvents lock token check (r18691, -94) + * fixed: repos-to-repos copy crash (r18451) + * fixed: 'svnmerge' utility improvements (r18811) + * translation updates for German, Swedish and Norwegian + + - Server: + * fixed: set svn:date at the end of commit in fsfs (r18078) + * fixed: don't wait for hook script background jobs (r18146) + * fixed: mod_dav_svn should log the whole error chain (r18211) + * fixed: uncomment section headers in repos config files (r18247, -50) + * fixed: log scalability issues with many paths (r18395, -404) + * fixed: better path input validation in mod_dav_svn (r18660) + * fixed: assert in copy in fsfs and bdb (issue #2398) + * fixed: RPM package bad interaction with NFS servers (issue #1456) + + - Both: + * fixed: copyright years updated to include 2006 (r18021, -127) + + Developer-visible changes: + * fixed: missing #include (r18065) + * fixed: allow building with Neon 0.25.5 (r18215) + * fixed: error leaks (18196, -249) + * javahl bindings: + - fixed: compile error on Visual Studio 2005 (r18054, -55) + * python bindings: + - fixed: libsvn_swig_py link problem on Solaris 10 (r17910) + - fixed: pool lifetime bug (r17992) + - fixed: memory leak (r18230) + - fixed: race condition during application pool initialization (r18721) + - fixed: Make pool parameters optional (issue #2444) + * ruby bindings: + - fixed: pool management issue (r17795, -811) + - fixed: protect baton from garbage collection (r17627) + - fixed: conversion bug (r17726, -925) + - fixed: compile errors with SWIG 1.3.24 (r18456, -58) + + +Version 1.3.0 +(30 December 2005, from /branches/1.3.x) +http://svn.apache.org/repos/asf/subversion/tags/1.3.0 + + User-visible changes: + - Client: + * 'svn ls -v' now shows remote locks (issue #2291) + * 'svn status' speedup (r15061, r15103) + * 'svn blame' speedup on files with long history (issue #1970) + * 'svnversion' now assumes default argument of '.' (r14892) + * support for neon 0.25.x, which fixes http:// control-c bug (issue #2297) + * support for more ISO-8601 date formats, compatible with GNU date (r14428) + * support for single-digit date components (r15459) + * on Windows, '_svn' admin dir now toggled by runtime env. variable (r16244) + * working copy size with empty propfiles reduced (r16855, see releasenotes) + * new switches added: + - 'svn blame --xml [--incremental]' (r14690) + - 'svn status --xml [--incremental]' (issue #2069) + - 'svn info --xml [--incremental]' + - 'svn add/import --no-ignore' (issue #2105) + - 'svnlook tree --full-paths' (r13976) + - 'svnlook diff --diff-copy-from' (r14855) + - 'svnlook changed --copy-info' (r16681) + * fixed: 'svn copy wc URL' might include deleted items (issue #2153) + * fixed: 'svn copy wc wc' allows cross-repository copies (issue #2404) + * fixed: 'svn up/merge' major property-merging bugs (issue #2035) + * fixed: 'svn merge' insisting on write access to '.' (issue #2411) + * fixed: 'svn merge' cross-device move problems (r16293, -329, -330) + * fixed: 'svn diff' outputs headers in wrong encoding (issue #1533) + * fixed: 'svn proplist/add/cat' dies on unversioned items (issue #2030) + * fixed: 'svn add' not honoring svn:ignore property (issue #2243) + * fixed: 'svn log -rN:M --limit X' error over http:// (issue #2396) + * fixed: 'svn switch --relocate' failure on 'deleted' dir (r16673) + * fixed: 'svn info' not always showing repos lock (issue #2276) + * fixed: 'svn info' might show lock on wrong path (r16626) + * fixed: 'svnlook' chokes on logs with inconsistent newlines (r14573) + * fixed: 'svnlook propget --revprop -t' failure (r15203) + * fixed: 'svnversion' wrongly traverses into externals (r15161) + * fixed: incorrect URI encoding passed to svn+ssh:// (issue #2406) + * fixed: properly handle filenames containing '@' (issue #2317) + * fixed: '--non-interactive' now suppresses launch of $EDITOR (r15277) + * fixed: conflict markers not in current encoding (r14621) + * fixed: commands ignoring extraneous -m or -F switches (issue #2285) + * fixed: poor error-checking when using revprops (r15542) + * fixed: stack-smashing bugs (r15948, r16037) + * fixed: incorrect parsing of mod_dav_svn XML responses (r17589) + * translation updates for all languages + + - Server: + * svnserve improvements: + - can now restrict read/write access by path (see releasenotes) + - undeprecation of the --read-only (-R) option (r17614) + * mod_dav_svn improvements: + - 'SVNListParentPath on' shows all repositories in web browser (r16158) + - ability to log high-level client operations (see releasenotes) + - sets svn:mime-type on autoversioning commits (r14359) + * 'svn log' performance improvement (r14722) + * fixed: fs history algorithm might return wrong objects (issue #1970) + * fixed: repos deadlock when hooks output too much (issue #2078) + * fixed: mod_dav_svn displays errors with sensitive paths (r14792) + * fixed: anonymous reader could create empty commits (issue #2388) + * fixed: possible segfault to callers of trace_node_locations() (r16188) + * fixed: BDB-style locking actions on FSFS repositories (r16295, r16297) + * fixed: numerous bugs running BDB commands on FSFS (issue #2361, r16388) + * fixed: svndumpfilter incorrectly remapping dropped revs (issue #1911) + + - Both: + * faster multiple (un)locks in a single svn:// request (issue #2264) + * the Subversion Book is no longer bundled (r17466) + + Developer-visible changes: + * reorganization of automated tests, including ability to run on ramdisk + * lots of Doxygen/API documentation cleanup + * numerous improvements to gen-make.py build system, especially on win32 + * working copy is now storing repos_root as separate field (issue #960) + * keywords are now stored in an internal hash (issue #890) + * client status APIs now makes more server-side info available (r16344) + * new public APIs: + - new transfer progress callback for DAV (r15948) + - svn_ra_initialize(), svn_client_open_ra_session() + - svn_fs_closest_copy(), svn_fs_type() + - several rev'd APIs, see doxygen docs + * SWIG bindings: No more compile-time or runtime SWIG dependencies + - SWIG/python bindings: + - automatic memory management: APIs no longer require pool arguments! + - improved stability, as shown by our new testsuite + - better error messages + - SWIG/ruby bindings: + - complete API coverage! + - automatic memory management + - greatly expanded test suite + - SWIG/perl bindings: + - new accessors for svn_lock_t, svn_fs_access_t + - a number of bugfixes + * javahl bindings: + - add streamy API for fetching file contents (r15584) + - fixed: let tests run before bindings are installed (issue #2040) + - fixed: lock command not raising errors properly (issue #2394) + - fixed: ignored errors from svn_client_blame2() (r16434) + + +Version 1.2.3 +(19 August 2005, from /branches/1.2.x) +http://svn.apache.org/repos/asf/subversion/tags/1.2.3 + + User-visible changes: + - Client: + * fixed: 'svn status -u' fails against pre-1.2 mod_dav_svn (r15359, r15423) + * fixed: 'svn export' segfault (r15516) + * fixed: 'svn merge' memory leak (r15233) + * fixed: horrible rename-tracing performance against 1.0 servers (r15315) + * fixed: 'svn cat' over file:// -- small leak (r15253) + * fixed: crash with "svn lock" and authentication (r15703) + * improvements to 'svnmerge' utility (r14008,-458,-587,-632, r15329,-340) + * translation updates for French, German, Polish, Norwegian, Swedish, + Korean + + - Server: + * fixed: mod_authz_svn being overly restrictive (r15463) + * fixed: fsfs directory caching bug (r15705, r15742) + + - Both: + * fixed: crash when >50 options passed to any commandline app (r15251) + * fixed: memory leak in character translation handle caching (r15379,-398) + + Developer-visible changes: + * fixed: crash when calling svn_client_(un)lock with no targets (r15734) + * rhel-4 RPM bugfix for python bindings (r15616) + * missing #include in SWIG bindings (r15683) + * javahl bindings: + - fixed: JNI library loading bug (r15552) + - fixed: JNI stack-name cut and paste error (r15337) + - fixed: crash when revisions have no dates (r15737) + * perl bindings: + - now compatible with SWIG 1.3.25 (r15248) + - allow SVN::Pool to be used as pool parameter (r15450) + - make SVN::Delta::Editor friendlier for debugging (r15609) + - fixed: wrap svn_ra_stat properly (r15713) + - fixed: bug in SVN::Core::Stream's read function (r15698, r15700) + * ruby bindings: + - now compatible with SWIG 1.3.25 (r14980, r15361) + + +Version 1.2.2 +(Not released, see changes for 1.2.3.) + + +Version 1.2.1 +(5 July 2005, from /branches/1.2.x) +http://svn.apache.org/repos/asf/subversion/tags/1.2.1 + + User-visible changes: + - Client: + * fixed: 'svn lock' on switched file locks wrong thing (issue #2307) + * fixed: 'svn (un)lock' errors on multiple targets (r14736, 14775) + * fixed: 'svn (un)lock' problems with URI-unsafe names (issue #2314) + * fixed: 'svn (un)lock' not caching authentication (r15088) + * fixed: 'svn unlock' loses executable bit (r14859, r14923, r14939) + * fixed: 'svn unlock URL' segfault (r14893) + * fixed: 'svn commit' failure on XML-unsafe locked paths (issue #2335) + * fixed: recursive directory copy bug (issue #2343) + * fixed: don't initialize RA library in 'svnversion' (r14755) + * fixed: svn-push segfault (r14732) + * various translation updates for localized client messages + + - Server: + * fixed: 'svn log' performance regression, general (r14116, 14772, 14759) + * fixed: 'svn log -v' performance regression, FSFS-specific (r15016) + * fixed: mod_dav_svn bug sets content-type incorrectly (r15046) + + Developer-visible changes: + * fixed: win32 innosetup's add/repair/remove features (r14830) + * fixed: OBOE with 'limit' parameter of svn_repos_get_logs3(). (r15119) + * redhat RPM fixes (r15050) + * perl bindings: + - accessors for svn_lock_t (r15082) + - call utf_initialize, adjust global pool usage (r15076, r15080, + r15081, r15117) + + +Version 1.2.0 +(21 May 2005, from /branches/1.2.x) +http://svn.apache.org/repos/asf/subversion/tags/1.2.0 + +See the 1.2 release notes for a more verbose overview of the changes since +the 1.1 release: http://subversion.apache.org/docs/release-notes/1.2.html + + User-visible changes: + - Client: + * add peg-rev syntax to co/blame/cat/ls/pget/plist/export (issue #1093) + * 'svn info' now works on URLs (r13123, 13144) + * 'svn* --version' now shows available repository back-ends (r13761) + * new fixed-length keywords (for placement in binary files) (issue #2095) + * on Windows, disk-cached passwords are now encrypted (r13888) + * performance improvements: + - 'svn status' does much less disk parsing (r11677, 11704) + - 'svn st -u' no longer asks server to generate textdeltas (issue #2259) + - 'svn revert -R' doing much less work (r13883) + - utf8<->native conversions are faster now (issue #2016) + * new switches added: + - 'svn commit --no-unlock - retain lock in wc upon commit + - 'svn log --limit N' - show only first N log messages + - 'svn info --revision' - show info on older object (r13265) + - 'svn list --xml' - output listing in XML + - 'svn propset --force' - allow unusual propsets (#2065) + - 'svn diff --force' - show diffs on binary files (#2099) + - 'svn co/up/st --ignore-externals' - skip over externals (#2189) + - 'svn export --non-recursive' - don't export subdirs (issue #2228) + - 'svnversion --help' - show help (r13128) + * fixed: 'svn merge' fails to add symlinks or expand keywords (issue #2064) + * fixed: 'svn merge --dry-run' shows spurious 'skip' messages (issue #1943) + * fixed: 'svn merge' file-not-found' error (issue #1673) + * fixed: 'svn merge' of propchanges into deleted file (issue #2132) + * fixed: 'svn merge' on implicit target with space (r13010) + * fixed: 'svn merge/diff URL URL' can cause httpd timeout (issue #2048) + * fixed: 'svn switch/update' failure might corrupt wc (issue #1825) + * fixed: 'svn up' should rm before add, helps case-insensitivity (r12616) + * fixed: 'svn up -rX' causes file to be unrestorable (issue #2250) + * fixed: 'svn copy wc wc' should keep .svn/ hidden (issue #1739) + * fixed: 'svn copy wc wc' of deleted=true doesn't delete (issue #2101) + * fixed: 'svn copy' shouldn't copy into schedule-delete area (issue #2020) + * fixed: 'svn copy dir dir' infinite recursion (issue #2224) + * fixed: 'svn log' throws error on unversioned target (issue #1551) + * fixed: 'svn log' in r0 working copy shows r1 log msg (issue #1950) + * fixed: 'svn export' bugs on deleted dirs or nonexistents (#2226, r13226) + * fixed: 'svn export' on single file from working copy (issue #1708) + * fixed: 'svn import' creating an empty revision (r14293) + * fixed: 'svn commit' ignores --encoding when editing externally (#2244) + * fixed: 'svn commit' log message lost if utf8-conversion failure (r13230) + * fixed: 'svn diff' output encoding bug (r11461) + * fixed: 'svn diff' showing prop-diffs on repos root dir (r13381-2) + * fixed: 'svn diff' label reversal (issue #2033) + * fixed: 'svn propget' prints extra newline in --strict mode (r14505) + * fixed: 'svn propset' should skip unversioned files (#2030) + * fixed: 'svn rm URL1 URL2 URL3...' huge memory usage (issue #2218) + * fixed: 'svn mkdir' cleanup after failure (r11883) + * fixed: 'svn status -u' crash in non-recursive wc's (issue #2122) + * fixed: 'svn revert' should skip unversioned items (issues #2030, 2133) + * fixed: 'svn revert' should suggest --recursive (issue #2114) + * fixed: 'svn add/import' better detects invalid paths (issue #1954) + * fixed: 'svn cleanup' should repair timestamps (r12012) + * fixed: 'svn cat -rBASE' contacts repository (issue #1361) + * fixed: fuzzily escape control-characters when sending over dav (#2147) + * fixed: prevent client from manipulating svn:wc:* properties (r12523) + * fixed: allow portnumber in svn+ssh://user@host:port/ URLs (r14373) + * fixed: xml-escaping bugs over dav (r11090) + * fixed: store symlinks as utf8, always work in non-utf8 locale (r11358-9) + * fixed: bug in special-file detranslation (r11441) + * fixed: show paths in local-style where we weren't (issue #1538) + * fixed: detect invalid propnames better (issue #1832) + * fixed: entire error stack not being printed (issue #1822) + * fixed: improper utf8 conversion of revision strings (issue #1999) + * fixed: use-commit-times timestamp bug (r12906) + * fixed: don't comment out section-names in default config file (r11771) + * more support for user-cancellation (r13083-4, 13086) + * improved error messages (r12920, 11392, 11599, 11913, #2154, #2214) + + - Server: + * mod_dav_svn autoversioning feature now complete (see release notes) + * 'svnadmin create' now creates FSFS repositories by default (r13624) + * new pre/post-revprop hook argument to describe propchange (r12162) + * mod_authz_svn groups can now contain other groups (issue #2085) + * 'svnadmin recover' now creates default svnserve passwd file (r11589) + * increase default BDB cache size in DB_CONFIG (r13030) + * new switches added: + - 'svnlook diff --no-diff-added' - suppress added files (#2180) + - 'svnlook propget/proplist --revprop' - show revision props (#2181) + - 'svnadmin load --use-pre-commit-hook' + 'svnadmin load --use-post-commit-hook'- invoke hooks when loading + * fixed: FSFS race condition on posix platforms (issue #2265) + * fixed: change FSFS revprops atomically and safely (issue #2193) + * fixed: FSFS should verify checksums (issue #2253) + * fixed: FSFS crash bug (r14333) + * fixed: 'svnadmin create' should clean up when it fails (r13200) + * fixed: 'svnadmin load' compatibility on pre-0.14 dumpfiles (r12075) + * fixed: 'svnadmin load' crashes on contentful rev 0 (issue #1674) + * fixed: 'svnadmin dump' should write in console encoding (issue #1997) + * fixed: check for null-streams in dump/load code (r10510) + * fixed: hook script ignored when symlink is broken (issue #1700) + * fixed: hook script may inherit server's stdin stream (r12155) + * fixed: potential svnserve segfault (r13199) + * fixed: svnserve handling mutually-exclusive options (issue #2251) + * fixed: mod_authz_svn should log errors to httpd errorlog (issue #2182) + * fixed: 'svnadmin hotcopy' failed to copy format files (r14678, r14683) + * mailer.py: add win32 compatibility, plus other bugfixes + + - Both: + * new 'locking' feature (issue #1478, see release notes for details): + - new: 'svn lock/unlock', 'svnadmin lslocks/rmlocks', 'svnlook lock' + - new: 'svn:needs-lock' property to enable communication + - 'svn st [-u]' shows local or remote lock overview + - 'svn info wc | URL' shows local or remote lock details + - 'svn commit' sends locks, 'svn up' removes stale locks + - new hook scripts: pre-lock, pre-unlock, post-lock, post-unlock + * speedups for 'svn blame' and other commands (see xdelta in release notes) + * fixed: make both svnserve and svn:// urls work with IPv6 (r13235-6) + * fixed: updating xml-unsafe dirname over http (issue #2268) + * new translation of localized messages: French + * continued improvement of localized message translations: + - German, Spanish, Polish, Brazilian Portuguese, Norwegian Bokmål, + Swedish, Traditional Chinese, Simplified Chinese, Korean, Japanese + - more localized messages in all svn-related binaries + + Developer-visible changes: + * binary diff algorithm now defaults to xdelta instead of vdelta + * huge number of new APIs: + - new locking APIs in svn_client.h, svn_ra.h, svn_repos.h, svn_fs.h + - new 'flattened' svn_ra.h API, which imitates svn_fs.h (issue #1931) + - new notification API in svn_client.h, svn_wc.h + - http://svn.haxx.se/dev/archive-2005-04/0319.shtml has all API changes + * fs now has its own 'format' file, independent of repos 'format' (r13387) + * improve efficiency of delta combining algorithm (r13016, r13063) + * make all BDB apis take explicit pool parameters (r13198, r13205) + * remove libsvn_fs_base caching of node revisions (r13299) + * libsvn_repos commit editor can now take incoming txn (r13733) + * fixed: mod_dav_svn sending illegal editor-drive (issue #2258) + * pool usage improvements (r12954, 12852, r13386, issue #1310) + * SWIG bindings: better API coverage overall. + - new ruby bindings! + - remove bitrotting swig-java bindings + - perl and python bindings: numerous improvements, see their own logs. + - bindings tests now within svntest framework + * javahl bindings: numerous improvements, see its own logs. + * many improvements to mailer.py and commit-email.pl + * rewrite/improvements to gen-make build system, including VS.NET support + * many improvements to the automated python testsuite (issue #2257) + * book moved to separate repository (http://svn.red-bean.com/svnbook) + + +Version 1.1.4 +(1 April 2005, from /branches/1.1.x) +http://svn.apache.org/repos/asf/subversion/tags/1.1.4 + + User-visible changes: + - Client: + * fixed: win32 not ignoring versioned symlinks (issue #2173) + * fixed: 'svn merge' can cause broken working copy (issue #2222) + * fixed: 'svn commit' fails when schedule-delete dir has local mod (r11980) + * fixed: 'svn st -u nonexistent_file' segfault (issue #2127) + * fixed: 'svn cp wc wc' utf8 conversion error (r13111) + * fixed: confusing error message about "wc not locked" (issue #2174) + * many translation updates for localized client messages + + - Server: + * fixed: nasty (though unusual) performance bug in FSFS commits (r13222-3) + * fixed: FSFS memory leak when auto-merging large tree (r13193) + * fixed: FSFS memory leak in 'svnadmin hotcopy' (r13218, 13465, 13468) + * fixed: FSFS segfault when encountering empty data reps (r13683) + * fixed: two dataloss bugs in svndumpfilter (r12630, r12636) + * fixed: wasteful memory usage in svndumpfilter (r12637, r12640) + * fixed: mod_dav_svn segfaults when client sends bogus paths (issue #2199) + * make mailer.py work on win32 (r12499, r12542, r12670) + + - Both: + * fixed: (win32) retry file operation if sharing violation (r12983, r12986) + + Developer-visible changes: + * add SWIG 1.3.24 and .25 compatibility (r12551, r12717-9, r12722, r13504) + * fixed: JavaHL run-time link error (r12576), path/url cleanups (r13090) + * fixed: python bindings log_receiver failure with SWIG 1.3.24 (r13487) + * build system tweaks: add install dependencies for fs & fs_base (r11050) + + +Version 1.1.3 +(14 January 2005, from /branches/1.1.x) +http://svn.apache.org/repos/asf/subversion/tags/1.1.3 + + User-visible changes: + - Client: + * translation updates for localized client messages. + + Developer-visible changes: + * Fix a compile error in the Perl bindings. + + +Version 1.1.2 +(20 December 2004, from /branches/1.1.x) +http://svn.apache.org/repos/asf/subversion/tags/1.1.2 + + User-visible changes: + - Client: + * fixed: 'svn switch' interruption can break working copy (issue #1826) + * fixed: 'svn switch' memleak over ra_dav (issue #2106) + * fixed: 'svn blame' algorithm bug (r11527) + * fixed: invoke external diff/diff3 with local-style paths (r11689) + * fixed: 'svn status' handling of missing subdirs (r11936) + * fixed: 'svn ls -v' encoding bug (r11740) + * fixed: 'svn ls "file with space"' bug (r12273, r12393) + * fixed: 'svn merge' should URI-encode copyfrom URLs (issue #1905) + * fixed: 'svn merge' deletion output formatting (r12100, r12111, r12114) + * fixed: 'svnversion --version .' crash (r11438) + * fixed: UNC paths on Cygwin (issue #2108) + * fixed: win98 iconv bug -- uninitialized variable (issue #2091) + * improved 'svn status' performance: + - do fewer check_path calls (r11592) + - 'svn status file' shouldn't recursively lock tree (r11439, r11669) + * translation updates for localized client messages. + + - Server: + * fixed: 'svnadmin load' race condition (r12327) + * fixed: fsfs memleak in commit finalization (r11706) + * fixed: fsfs memleak in inefficient directory removal (r11701) + * fixed: fsfs commits use insert-only perms on db/revs/ (r11665) + * fixed: fsfs creates lockfile at creation time, not at 1st commit (r12172) + * fixed: svndumpfilter mislabeling output as version 3 (issue #2142) + * fixed: 'svnserve -h' encoding bug (part of issue #1997) + * fixed: prevent cross-repository copies (r12003) + * fixed: increase log-region max size in default DB_CONFIG (issue #2159) + + - Both: + * fixed: 'svn switch' quietly corrupting working copy (issue #2124) + * fixed: canonicalize paths sent by ra_svn/svnserve (issue #2119) + * fixed: memleak into UTF8 translation routines (r11689) + + Developer-visible changes: + * add support for BerkeleyDB 4.3 (if using a compatible apr-util) + * add support for any apr/apr-util 1.X + * disallow incompatible SWIG versions (r12450) + * fixed: slight API/ABI incompatibility between 1.0.9 and 1.1.x (r12102) + * fixed: perl bindings pool usage & object refcounts (r11451, r11630) + * fixed: perl bindings pool usage and potential memleak (r12397) + * fixed: javahl crash trying to fetch nonexistent property (r12184) + * fixed: javahl build can fail due to missing dirs (issue #2032) + * fixed: RPM build breakage (issue #2111) + * fixed: i18n issues for windows installer (r11685) + * allow build system to update single .po file (r11763) + + +Version 1.1.1 +(22 October 2004, from /branches/1.1.x) +http://svn.apache.org/repos/asf/subversion/tags/1.1.1 + + User-visible changes: + - Client: + * fixed: 'svn status' win32 performance regression (issue #2016) + * fixed: 'svn ls' dying on non-ascii paths over DAV (issue #2060) + * fixed: allow URI-encoded colon or pipe on win32 (issue #2012) + * fixed: broken win32 UNC paths (issue #2011) + * fixed: memory bloat when committing many files over DAV (r11284, -321) + * fixed: eol-style translation error for 'svn propget' (r11202, -243) + * fixed: 'svn propedit' does EOL conversion properly (issue #2063) + * fixed: 'svn log --xml' shouldn't be locale-dependent. (r11181) + * fixed: 'svn export' of symlinks with 'use-commit-times' (r11224) + * fixed: 'svn export -rBASE' when WC has added items (r11296, -415) + * many translation updates for localized client messages. + + - Server: + * fixed: 'svn ls' HTTP performance regression (r11211, -232, -285) + * fixed: make it possible to set "SVNPathAuthz off" in httpd.conf (r11190) + * fixed: fsfs validating revisions when accessing revprops (issue #2076) + * fixed: 'svn log -v' hiding too much info on 'empty' revisions. (r11137) + * fixed: encoding bug with 'svnlook log'/'svnlook author' (r11172) + * fixed: allow mod_authz_svn to return '403 Forbidden', not 500 (r11064) + * fixed: XML-escape author and date strings before sending (issue #2071) + * fixed: invalid XML being sent over DAV (issue #2090) + + Developer-visible changes: + * fixed: IRIX compile error (issue #2082) + * fixed: error in perl bindings (r11290) + * fixed: error leaks in mod_dav_svn (r11458) + * fixed: javahl should use default config directory (r11394) + + +Version 1.0.9 +(13 October 2004, from /branches/1.0.9) +http://svn.apache.org/repos/asf/subversion/tags/1.0.9 + + User-visible changes: + - Server: + * fixed: 'svn ls' HTTP performance regression (r11211, -232, -285) + * fixed: 'svn log -v' hiding too much info on 'empty' revisions. (r11137) + + Developer-visible changes: + * fixed: make redhat 7/8 rpm scripts build the book correctly (11143) + + +Version 1.1.0 +(29 September 2004, from /branches/1.1.x) +http://svn.apache.org/repos/asf/subversion/tags/1.1.0 + +See the 1.1 release notes for a more verbose overview of the changes since +1.0.x: http://subversion.apache.org/docs/release-notes/1.1.html + + User-visible changes: + * new non-database repository back-end (libsvn_fs_fs) + * symlinks can now be placed under version control (unix systems only) + * cmdline client now supports psuedo-IRIs and autoescapes chars (issue #1910) + * 'svnadmin recover' no longer waits forever for a lock (new '--wait' option) + * new $Revision$ synonym for $Rev$ and $LastChangedRevision$ + * new runtime option 'store-passwords = ' gives finer control (r10794)x + * fixed: working copies now shareable by multiple users (issue #1509) + * fixed: diff and other subcommands correctly follow renames (issue #1093) + - new 'peg' syntax for diff/merge: 'svn diff -r X:Y TARGET@REV' + - now able to compare working copy with URL: 'svn diff --old WC --new URL' + * new framework for localized error/info/help messages, initial translations: + - German, Polish, Swedish, Norwegian Bokmål, Traditional Chinese, + Japanese, Brazilian Portuguese. + * speed improvements: + - faster 'svn up' on complex working copies -- no more repos txns (r8840) + - faster 'svn status' -- fewer stat() calls (r9182) + - faster 'svn checkout' -- fewer sleep() calls (r9123) + - faster 'svn blame' -- new RA->get_file_revs() func (issue #1715) + * new switches added: + - 'svn blame --verbose' - show extra annotation info + - 'svn export --native-eol TYPE' - export using TYPE line-endings + - 'svn add --force' - recurse into version-controlled dirs + - 'svnadmin dump --deltas' - include binary diffs in dumpfile + - 'svnadmin create --fs-type fsfs' - create fs_fs repos (default is bdb) + - 'svnserve --tunnel-user=NAME' - assume authenticated NAME over tunnel + - 'svndumpfilter [cmd] --quiet' - less chatty dumpfiltering + - 'svnserve --version' - show program's version + 'svnversion --version' + 'svndumpfilter --version' + * svnadmin dump/deltify now understand -r{DATE} (r9805) + * allow update of non-existent target entry (partial issue #1902 fix) + * 'svnadmin create' now sets sgid bit on repos/db/ (unix systems only) + * increase default neon (ra_dav) timeout from 120 to 3600 seconds (r9568) + * print verbose BDB error messages (r10557, r10566) + * fixed: don't bail when 'svn up' refuses to delete local mods (issue #1806) + * fixed: process svn:externals in defined order (issue #1788) + * fixed: pass new propval to stdin of pre-revprop-change hook (issue #952) + * fixed: svndumpfilter logic/memory/display bugs (r8691, 8831, 9061) + * fixed: 'svnadmin hotcopy PATH .' (r8659) + * fixed: copy crash bug (r8863) + * fixed: 'svn st -u' crash bug (r10841) + * fixed: 'svn commit' segfault (r10676) + * fixed: allow cleanup on .svn/ dirs containing KILLME file (r8891) + * fixed: 'svn revert' detects corrupted text-base (r8897) + * fixed: 'svn status -N' no longer locks entire tree (r8906) + * fixed: several different 'svn switch' bugs (r9192, 9203, 9238, 9698) + * fixed: some 'svn copy' bugs (r9193, 9274) + * fixed: obscure update-deletion bug (r8976) + * fixed: utf8 conversion 'hang' (r9233) + * fixed: missing UTF8->native recoding in 'svn log' output (r10652, 10673) + * fixed: 'svn blame' now defaults to rev (r9440) + * fixed: 'svn blame' closing files before deleting them (issue #1969) + * fixed: 'svn diff' shows truncated paths (r9693) + * fixed: 'svn diff --notice-ancestry' bug (r9699) + * fixed: 'svn subcommand -r{DATE} URL' works if URL not in HEAD (issue #1840) + * fixed: 'svn blame' on non-ascii path truncation (issue #1770) + * fixed: svn:external 'wc not locked' bug (issue #1897) + * fixed: proper mod_dav_svn html/xml escaping (issue #1209) + * fixed: memleak in 'svn propset -R URL' (issue #1928) + * fixed: stop 'svn up' from deleting schedule-add target dir (issue #1793) + * fixed: 'svn merge' adding a directory already 'deleted' (issue #1769) + * fixed: excessive memory use when fs deltifies revision 2^N (r10070) + * fixed: disallow non-recursive directory commit (issue #1797) + * fixed: allow propget of props with colon in name (issue #1807) + * fixed: 'svnadmin load' computation of copyfrom-rev (issue #1795) + * fixed: runtime config files created with proper line-endings (issue #1803) + * fixed: make svnserve's authn work on usernames with spaces (r10385) + * fixed: have svnserve use repos UUID as default authn realm (r10394) + * fixed: segfault when history-following hits 'empty' revision (r10368) + * fixed: overzealous out-of-dateness checks in 'svn cp wc URL' (issue 1994) + * fixed: don't URI-encode path in mod_dav_svn XML listings (r10461) + * fixed: 'svn info' should refuse URL targets (r10760) + * fixed: incomplete-directory handling bug (r10956) + * fixed: allow cancellation between files during recursive dir add (r10894) + * general improvement and normalization of error messages + * many improvements to contributed tools: mailer.py, psvn.el, etc. + + Developer-visible changes: + * libsvn_fs now loads either bdb (libsvn_fs_base) or fsfs (libsvn_fs_fs) + * new console-printing API: svn_cmdline_printf() family checks for errors. + * new library-version querying API: + - new svn_[libname]_version() in each library + - svn_ver_*() family of functions + * 2nd generation APIs, from svn_foo() --> svn_foo2(). old APIs deprecated. + - svn_wc_adm_open2() & friends, svn_wc_export2(), svn_client_add2() + svn_wc_parse_externals_description2(), svn_hash_read/write2(), + svn_repos_dump/load_fs2() & friends, svn_wc_diff2(), + svn_subst_copy_and_translate2() + * other new APIs: + - svn_stream_copy(), svn_txdelta_target_push(), svn_opt_parse_path(), + svn_io_file_flush_to_disk, svn_repos_trace_node_locations(), + svn_repos_get_file_revs(), RA->get_locations(), RA->get_file_revs, + RA->get_version(), svn_sort_compare_paths(), svn_utf_initialize() + * SVN_REVNUM_FMT_T usage replaced with %ld (r9691) + * cache mod_authz_svn authz file per connection (r8867) + * validate hex digits in % escape (issue #1947) + * hashes now written to disk in sorted order (r9910) + * do cancellation checks before loops, not after (r8918) + * fixed: bug in svn_repos_dir_delta replacement logic (r8078) + * fixed: tiny memory access bugs (r8229, 8230, 8313) + * fixed: several commit buglets (r8955, 9658, 9757, 9855) + * fixed: don't recursively lock all prop commands (r9172) + * fixed: svnserve memory usage on many-file commits (r9185) + * fixed: close svnserve child's listen-socket after forking (r10050) + * fixed: 'svnadmin hotcopy' integrity improvements (issues #1817, #1818) + * fixed: only verify media type of svn:mime-type, not encoding (r10126) + * fixed: handle '//' and '..' in svn_path_canonicalize (issue #1779) + * fixed: double URI escaping (issue #1814) + * fixed: editor-driver bug (don't delete before every copy) (r10851) + * fixed: potential mod_dav_svn crashes/memleaks (r10478) + * fixed: better 'svnadmin verify verification (r10508, r10509) + * fixed: encoding of get_repos_url_result (r10353, 10375) + * fixed: prevent canonicalized URIs from ending in '/' (r10317) + * stop using -std=c89 gcc flag (r11054) + * sync with apr 1.0's find_apr.m4 and find_apu.m4 files (r10560) + * win32 installer improvements (r10978) + * huge improvements to python, perl, java bindings + * huge changes to win32 build system + + +Version 1.0.8 +(22 September 2004, from /branches/1.0.8) +http://svn.apache.org/repos/asf/subversion/tags/1.0.8 + + User-visible changes: + * fixed: mod_authz_svn path and log-message metadata leaks. + See CAN-2004-0749, and descriptive advisory at + http://subversion.apache.org/security/CAN-2004-0749-advisory.txt + + +Version 1.0.7 +(17 September 2004, from /branches/1.0.x) +http://svn.apache.org/repos/asf/subversion/tags/1.0.7 + + User-visible changes: + * fixed: win32 'file not found' error [issue #1862] + * fixed: 'svn st -u' crash (r10841) + * fixed: potential repos corruption; ensure stdin/out/err always open (r10819) + * fixed: allow propnames containing ":" to be fetched via http:// (r10190) + * fixed: allow user to interrupt between authentication prompts (see r11014) + * fixed: work around +t directory-creation bug in APR (r10616, 10638, 10642) + * various small fixes to Book + + Developer-visible changes: + * fix library dependencies for bindings (r9338, 9340) + * java bindings: fix a crash and other bugs (r9883, 9905, 8027) + * perl bindings: various fixes (see r11023) + + +Version 1.0.6 +(19 July 2004, from /branches/1.0.x) +http://svn.apache.org/repos/asf/subversion/tags/1.0.6 + + User-visible changes: + * fixed: crash in status command, caused by race (r10144) + * fixed: crashes when deleting a revision-prop (r10148, r10185, r10192) + * fixed: mod_authz_svn allows COPY method on repos with space in name (#1837) + * fixed: mod_authz_svn COPY security hole: authorize whole tree (issue #1949) + + Developer-visible changes: + * neon 0.24.7 now required (fixes wire compression bugs) (r10159, 10176) + + +Version 1.0.5 +(10 Jun 2004, from /branches/1.0.5) +http://svn.apache.org/repos/asf/subversion/tags/1.0.5 + + User-visible changes: + * fixed: security bug in svn protocol string parsing. (CAN-2004-0413) + + +Version 1.0.4 +(21 May 2004, from /branches/1.0.x) +http://svn.apache.org/repos/asf/subversion/tags/1.0.4 + + User-visible changes: + * fixed: 'svn up' can delete unversioned data on win32 fs (issue #1854) + * fixed: pool leaks in 'svnlook diff/changed/dirs-changed' + * fixed: insecure script example in pre-commit-hook template + * fixed: inability to do a checkout to '/' + * officially recommend neon 0.24.6 in all docs. + + Developer-visible changes: + * fixed: RPM build for Fedora & WBEL3/RHEL3 + * fixed: SWIG-java building problem + * fixed: javahl bug which can crash JVM + * fixed: change formatting codes in svn_swig_pl_callback_thunk + * fixed: properly wrap svn_txdelta_parse_svndiff for perl + + +Version 1.0.3 +(19 May 2004, from /branches/1.0.3) +http://svn.apache.org/repos/asf/subversion/tags/1.0.3 + + User-visible changes: + * fixed: security bug in date parsing. (CAN-2004-0397) + + +Version 1.0.2 +(15 April 2004, from /branches/1.0.x) +http://svn.apache.org/repos/asf/subversion/tags/1.0.2 + + User-visible changes: + * fixed: segfault when remotely deleting svn:author property. + * fixed: mod_dav_svn accepting too many authors. (issue #1786) + * fixed: create runtime config files with native EOLs. (Issue #1802) + * fixed: recursive propset can corrupt .svn/entries (issue #1794) + * fixed: allow shared working copies [mostly working now] (issue #1509) + * fixed: mod_authz_svn should ignore uri on MERGE request (partial #1821) + * fixed: svnserve assertion failure on empty error messages + * fixed: commit/update memory leaks when working on many targets (issue #1635) + * fixed: don't display repos-paths or URLs with '\' on win32. + * new example script: svnserve 'sgid' wrapper. + * minor book fixes, new 'best-practices' doc. + + Developer-visible changes: + * fixed: deprecation warning from SWIG 1.3.20_ + * fixed: broken win32 python-swig bindings compilation. + * fixed: bug in libsvn_fs changes-table change-folding code. + * fixed: perl bindings: wrap root->paths_changed, apply_txdelta return values + * added VC7 support and defines for including debug symbol files. + + +Version 1.0.1 +(12 March 2004, from /branches/1.0.x) +http://svn.apache.org/repos/asf/subversion/tags/1.0.1 + + User-visible changes: + * allow anonymous access checking in mod_authz_svn + * fixed: mod_authz_svn now works with SVNParentPath (issue #1588) + * fixed: potential segfault in mod_dav_svn. + * fixed: improper BDB cursor shutdown in libsvn_fs, which can wedge repos. + * fixed: allow checkout of repository with space in path. (issue #1694) + * fixed: make 'svn propget URL' work correctly over svn://. (issue #1752) + * fixed: failed 'svn merge URL' when URL contains user@host. (issue #1759) + * fixed: invalid REPORT response when updating a deleted wc. (issue #1721) + * fixed: allow deletes below copied wc dirs. + * fixed: merge --dry-run bug on added-files with props. (issue #1738) + * fixed: svnlook no longer requires write access to '.' + * fixed: ensure 'svn blame' fails on files marked as binary. (issue #1733) + * fixed: make failed direct-URL commits clean up their fs txns. (issue #1726) + * fixed: obscure bugs in time/date string formatting. (issue #1692) + * fixed: svn export doesn't export svn:externals. (issue #1750) + * fixed: svn import doesn't handle EOL or keyword translation. (issue #1756) + * fixed: svn status -v shows unwanted status of externals (issue #1741) + * fixed: allow revert of schedule-replace file that has no props (issue #1775) + * fixed: svnserve segfault on invalid --listen-host argument. + * fixed: switch bug which caused wrong URL to be left in wc. + * detect invalid UTF8 filenames when native locale is UTF8. + * improve presentation of directory property conflicts. + * improve presentation of errors from svnadmin & svnlook. + * clarify output of 'svnadmin help deltify'. + * augment copyright notice to --version output. + * more book updates. + + Developer-visible changes: + * remove obsolete auth provider examples. + * prevent potential ra_dav commit race-condition. + * fix svn_io_dir_walk 'dot-first' ordering required by 'svnadmin hotcopy'. + * fix error leaks in dav_svn_convert_err() + * upgrade win32 innosettup tools and redhat RPMs. + * fix compile warning: compressed streams on LP64 architecture. + * use cpio to generate tarballs instead of GNU tar. + * tweaks to dist.sh. + * fix bindings on win32. + * fix perl bindings build on OS X. + * fix perl bindings: bug which rejects string revnums. + + +Version 1.0.0 +(branching 23 February 2004, from /branches/1.0.x) +http://svn.apache.org/repos/asf/subversion/tags/1.0.0 + + User-visible changes: + * fixes to the shbang lines in tools/hook-scripts/. + * vast improvements to cvs2svn.py (NOTE: now a separate project!) + * general documentation cleanup: + - clarify built-in help text for 'svn switch' and 'svn status'. + - fix docs within the hook templates. + - cleanups to README, INSTALL, HACKING, svn-ref.tex, bash_completion. + - bring www/ pages up-to-date for 1.0. + - many changes to the Book + + Developer-visible changes: + * updates to the win32 installer packaging code. + * cleanups to SWIG bindings: + - disable svn_io_* functions. + - svn_filesize_t and apr_time_t fixes. + - remove debugging print statements and various warnings. + - make svn_repos_dir_delta() function correctly + - add support for repos authz callback. + + +Version 0.37.0 [Beta Interim 2] +(branching 24 January 2004, from /branches/1.0-stabilization) +http://svn.apache.org/repos/asf/subversion/tags/0.37.0 + + User-visible changes: + * bugfix: buffer overflow for AIX client + * 'svn merge' now notices ancestry by default. (r8390) + * bugfix: double Ctrl-C on windows no longer wedges repository. + * New date formats (see API change: Rewrite of date parser below) + * bugfix: Errors in authentication when --no-interactive is turned on (r8139) + * bugfix: Fix some 'access denied' errors on Windows (r8341, r8352) + + Developer-visible changes: + * API change: Rewrite of date parser (r8327, r8328, r8329) (issue #408) + * bugfix: svn_fs__bdb_changes_fetch() fouls up change ordering (issue #1695) + * require SWIG >=1.3.19 (issue #1690) + * numerous changes to language bindings, to keep up with C API. + * fix: apr build issues (r8279, r8280, r8318) (issue #1666) + * changed the auth-provider C API to use 'realmstring' on all funcs + * check the ra plugin ABI versions. + * fix: ABI problem with blame. (r8494) (issue #1705) + * remove svn_io_file_printf from public API. (r8492) (issue #1653) + * extensive changes in the perl client bindings. (r8270) + * too many big and small internal code cleanups and fixes to mention here + + + +Version 0.36.0 [Beta Interim 1] +(branching 13 January 2004, from /branches/1.0-stabilization) +http://svn.apache.org/repos/asf/subversion/tags/0.36.0 + + User-visible changes: + * add cancellation support to svnadmin and svnlook (r8222) + * runtime 'store-password' option renamed to 'store-auth-creds' (r8014) + * 'svn blame' changes: + - now shows correct revision info (r8035-6) + - responds to cancellation better (r8129) + * svnserve changes: + - added '--inetd' option; now required to speak with stdin/stdout (r8205) + - added '--listen-port' and '--listen-host' options (r8001-2) + - removed '-u' option (r8003) + - ignore SIGPIPE (no more repos lockups when you terminate a pipe) (r8140) + * lots of Book work (many newly-documented Apache and svnserve topics) + + Developer-visible changes: + * bugfix: svnserve network crash (r8142) + * bugfix: return result_rev from svn_client_checkout correctly (r8096) + * bugfix: fs history harvesting code (r8154) + * bugfix: memory leak in mod_dav_svn (r8223) + * bugfixes in edge-cases of status and update (r8114-5) + * make 'svn blame' work with 18n and uri-escaped filenames (r8023, 8030, 8040) + * small bugfixes to authentication system (r8006, r8235) + * standardize error message formatting (r8218) + * load RA modules as foo.so.0, not foo.so (r8098) + * various core API changes: + - use constructor for svn_client_cxt_t (r8053-4) + - anchor/target may use NULL for target (r8216) + - stop using apr_ symbols (r8219) + - rename to 'svn_repos_authz_func_t' (r8213) + - add pool parameter to finish_report and abort_report (r8215) + * numerous changes to Perl and Java bindings, to keep up with C API. + + + +Version 0.35.1 [Beta] (branching 19 December 2003, from /tags/0.35.0) +http://svn.apache.org/repos/asf/subversion/tags/0.35.1 + + NOTICES: + + This release is to correct for the problems in the 0.35.0 + release and affects Windows users only: + + * fix: file handle leak (r8048) + * fix: UTF-8 path problem (issue #1660) + + +Version 0.35.0 (branching 12 December 2003, from revision 7994) +http://svn.apache.org/repos/asf/subversion/branches/0.35.0 + + NOTICES: + + 1. As of this release, Subversion once again does deltification + automatically. This means that the deltification step most + repositories introduced into their post-commit hooks as of + release 0.33.0 should now be reverted. Look for a line with + "svnadmin deltify" in hooks/post-commit, and remove it. + + 2. We now recommend using Berkeley DB 4.2.52 or higher for SVN + repositories. See http://sleepycat.com/download/index.shtml. + + User-visible changes: + * BDB log files are automatically pruned, with BDB 4.2.50 and higher (#1615) + * deltification is automatic again (issue #1601) + * fix: svn diff -rX:Y wcpath' may lie (issue #1616) + * fix: URI-decoding problem on 'svn import' (issue #1622) + * many other enhancements, minor features, and bugfixes not listed here + + + Developer-visible changes: + * misc. improvements on Perl and Java bindings + * improved diff handling (r7985) + * many other changes not listed here + + + Merged revisions after release branching: + * r8009, r8010 and r8011 - Java bindings + * r8041 - typo/bugfix + + +Version 0.34.0 (released 3 December 2003, from revision r7859) +http://svn.apache.org/repos/asf/subversion/tags/0.34.0 + +##################################################################### +## WARNING WARNING WARNING WARNING WARNING WARNING WARNING ## +##################################################################### +## ## +## This release makes an incompatible change to the Subversion ## +## database. Repositories created with versions of Subversion ## +## prior to 0.34 will not work with Subversion 0.34. ## +## To upgrade, first use 'svnadmin dump' with your existing ## +## Subversion binaries. Then upgrade your binaries to 0.34, and ## +## use 'svnadmin load' to create a new repository from your ## +## dumpfile. ## +## Don't forget to copy any custom configuration/hooks from the ## +## old to the new repository. ## +## ## +##################################################################### + + Please see notes/repos_upgrade_HOWTO for documentation on migrating + pre-0.34.0 repos to 0.34.0. + + That document is also located here: + http://svn.apache.org/repos/asf/subversion/trunk/notes/repos_upgrade_HOWTO + + User-visible changes: + * fs schema change (#1578, #1595) **NOTE: repos dump/load cycle required!** + * Berkeley DB 4.2.50 is now the recommended Berkeley version + * Fix: 'svn status' thought replaced items were unversioned (#1609) + * SSL server cert error prompt improvement (r7849) + * many error message improvements (r7745, r7763, r7824 and 7827 - #897) + * don't show update-completion message until all wc work completes (#1556) + * many other enhancements, minor features, and bugfixes not listed here + + Developer-visible changes: + * public client APIs changes (r7799) after fixing #1556 + * many improvements and fixes on Perl bindings (perl => 5.8.0 are required) + * improvements, fixes on misc. test scripts + * many other changes not listed here + + Merged revisions after release branching: + * r7868 - Java bindings + * r7888 - Security fix for svnserve + + +Version 0.33.1 (released 17 November 2003, revision r7782) +http://svn.apache.org/repos/asf/subversion/tags/0.33.1 + + NOTICE: This is a bugfix release. The bug is fixed if *either* + the client or server uses the new code. + +User-visible changes: +* major performance fix for updates + + +Version 0.33.0 (released 13 November 2003, revision r7737) +http://svn.apache.org/repos/asf/subversion/tags/0.33.0 + + NOTICES: + + 1. This client may be incompatible with ra_dav servers <= 0.31. + + 2. In order to make commits more responsive, repository + deltification is no longer automatic. However, you may want + to run deltification as a background process in your repository + post-commit hook. For example, the new post-commit.tmpl file + recommends 'nice -2 svnadmin deltify "$REPOS" -r "$REV" &'. + + User-visible changes: + * now require APR/APU 0.9.5 (ships in Apache 2.0.48) + * lose automatic deltification, but recommend it in post-commit (r7695, #1573) + * new configuration and authn/authz support in ra_svn (r7604, r7601) + * much faster checkouts and updates, over both svn:// and http:// (#1429) + * new partial-authz feature: checkouts/updates just skip unauthorized items + * new 'use-commit-times = yes' config option to use commit-time timestamps + * new 'svnadmin hotcopy' command, like hot-backup.py (#1567) + * fix Win32 "access denied" error in renames (r7598, #1576) + * unnecessary working copy tree locks now avoided, to save time (#1245) + * Compatibility changes: + - lose ra_dav compatibility with servers 0.31 and earlier + - lose support for working copy format "1" (not created for over a year) + * 'svn diff' and other read-only actions now work in read-only working copies + * 'svn blame -rX' now does the intuitive thing + * 'svn log' output headers now say "rXXXX | " instead of "rev XXXX: " + * 'svnversion' no longer stymied by svn:externals + * new 'svn pd' alias for 'svn propdel' + * '-rCOMMITTED' keyword now works on more commands + * minor changes to output of 'svn ls -v' and 'svn st -v' (r7530) + * 'svn log --xml' now obeys the '-q' flag (r7555) + * cvs2svn.py bugfixes, especially issue #1440 + * book and documentation updates + * removed server config options ssl-ignore-invalid-date and + ssl-override-cert-hostname (r7644) + * many other enhancements, minor features, and bugfixes not listed here + + Developer-visible changes: + * repair text- and prop-time in .svn/entries if spuriously wrong (r7565) + * speed up keyword translation (r7502) + * two new editor functions, absent_file() and absent_directory() + * ra_dav checkouts/updates no longer do O(n) number of GET, PROPFIND requests + * new svn_io_temp_dir function, will morph to apr_temp_dir_get soon + * new svn_io_file_close wrapper for apr_file_close + * tools/test-scripts/svntest/ scripts now support ra_dav and ramdisk + * many other changes not listed here + + +Version 0.32.1 (released 23 October 2003, revision 7497) +http://svn.apache.org/repos/asf/subversion/tags/0.32.1 + + NOTICE: This release is to correct for the problems in the 0.32.0 + release. There are no user or developer changes in this release + other than the subversion/include/svn_version.h now reflects + the correct version number. + + NOTICE: This release of Subversion causes an ra_dav client/server + compatibility break with Subversions older than 0.28.0. + +Version 0.32.0 (released 22 October 2003, revision 7480) +http://svn.apache.org/repos/asf/subversion/tags/0.32.0 + + NOTICE: This release of Subversion causes an ra_dav client/server + compatibility break with Subversions older than 0.28.0. + + User-visible changes: + * new 'svn blame' subcommand. (r7389, 7438, #508) + * fix huge ra_dav 'svn import' memory leak. (r7381) + * other bugfixes: proper line endings in diff headers (r7450, #1533), + stop auto-props from removing all whitespace (r7358), 'svn st' UI + consistency fix (r7364), various 'svn switch' fixes (r7366), + mini-manpages for svnadmin, svnserve, svnversion (r7421), remove + 'P' field from 'svn ls -v' (r7432), 'svn merge' double-notification + bug (r7447), prevent 'svn:externals' infinite loop (r7459), 'svn + merge' segfault (r7458). + + Developer-visible changes: + * 'svn diff' is now reasonably streamy. (r7393, 7439, #1481) + * fix many ra_dav pool abuses. (r7370-3, 7380, 7368, 7400, ...) + * fix mini leaks: clear unused svn_error_t's. (r7378-9, 7405, 7408, 7429) + * tons of code, doc, API cleanup. (from julianfoad!) + * new RA->get_repos_root() API. (r7428) + * swig/python, swig/perl and native JNI updates and improvements. + * more work on build depenedency generator. (r7412-8) + * svn_repos_finish_report() now aborts txns on error. (r7424) + * remove crufty old ra_dav compatibility code (r7466, 7468) + * other changes: new SVN_DEBUG_ERROR tool macro, new 'davautocheck' + and 'contrib' makefile targets, new --enable-gprof configure option + (r7437), new scramble-tree.py testing tool, auth provider + reorganization, make RA->get_dir fetch props correctly over ra_dav + (r7431), notice permission error when creating unique tmpfile (r7434). + + +Version 0.31.0 (released 08 October 2003, revision 7355) +http://svn.apache.org/repos/asf/subversion/tags/0.31.0 + + User-visible changes: + * new 'svnlook history' command (and removal of 'svnadmin lscr'). + * new 'auto-props' feature can set file properties during 'svn add/import' + * win32 client now properly converts UTF8 to console-locale. (r7168, #872) + * 'svn up' now notices when svn:externals value changes. (r7256, #1519) + * authentication changes: + - client caches auth-creds in memory for a single session (r7292, #1526) + - SSL cert caches keyed on host+port, not any SSL connection. (r7174) + * faster 'svn log' (see new fs-history algorithm) (#1499) + * faster repos read-operations (caching gives ~20% speedup) (rXXXX, #1499) + * faster updates (fewer entries-file writes gives ~20% speedup) (r7170, #1490) + * more work on psvn.el and svn_load_dirs.pl + * more cvs2svn bugfixes + * obsolete manpages truncated to point to 'help' and book URLs. (r7340, #1508) + * other bugfixes: no more revision keywords "FIRST" or "CHANGED" (r7250), + fix 'svn cp URL URL' $EDITOR msg generation (r7264), fix regression + bug in 'svnadmin load' (r7273), 'svnadmin setlog' now triggers + repository hooks (r7322), 'svn cp -rHEAD wc' now works correctly (r7331), + post-commit-hook failures correctly ignored by client (r7342, #906) + + Developer-visible changes: + * tons of filesystem improvements (#1499): + - new fast fs-history algorithm: allows stable VR urls (r7283, #1499) + - new dag-node caching (r7163) + - skip-deltas now run in individual trails (r7138) + - no-op svn_fs_copy()s don't write to the database (r7158) + * mod_dav_svn MERGE response is faster (using svn_repos_replay()) (r7191) + * ensure consistent wc 'dead entry' cleanup (r7197, r7204, #1075) + * lots of work on gen_win.py, gen_make.py, gen_base.py tools + * lots of work on making SWIG-java bindings build. + * updates/improvements to javahl bindings and SWIG-perl bindings + * updates/improvements to Mandrake RPM builds + * other bugfixes: python testsuite now uses local path separators (r7224), + svn:externals no longer keeps connections open (r7312, #1448), + UTF8-to-local date conversion (r7316, #1534), API consistification + changes (r7298, r7302, r7304, r7307). + + +Version 0.30.0 (released 24 Sep 2003, revision 7178) +http://svn.apache.org/repos/asf/subversion/tags/0.30.0 + + User-visible changes: + * SSL changes: (r7134, #1330) + - client now prompts to cache server certificates + - no more 'ssl-ignore-unknown-ca' option + - 'ssl-ignore-host-mismatch' is renamed to 'ssl-override-cert-hostname' + - new 'ssl-trust-default-ca' option to trust 'default' openssl CAs + * 'svn log' no longer dies on unversioned args (r6989, #777) + * local mods now obstruct 'svn up' deletions (r7050, #1196) + * 'svnserve' now notices (unauthenticated) --username arg (r7060) + * no more 'svnadmin createtxn' subcommand. (r7062) + * 'svn ls -v' shows years when appropriate + * document some new things in Book (r7014), plus minor technical fixes + * website changes: new sidebar, new 'svn links' page, new tigris.org!! + * other bugfixes: hooks use proper stdout handles on win32 (r7001), + prevent copies of copies in wc (r7077, #1259), display failed + revprop change over ra_dav (r7081, #1520), 'svn st -u' throws RA + error properly (r7094, #1506) + + Developer-visible changes: + * ra_dav now requires neon-0.24.X + * many gen_make.py/gen-base.py improvements, especially for win32 builds + * many improvements to swig/perl bindings + * improvements to contrib/: psvn.el, and new svn-push program. + * more cvs2svn bugfixes: issue #1504, #1421, #1514, and new --username arg. + * python testsuite only raises exceptions, never status codes. (#1192) + * various libsvn_fs re-org (prepwork) for issue #1499. + * other bugfixes: code-complete timestamp feature (r6983, #1445), add + op-counting features to trails (r6984, #655), fs UUID caching + (r7037), almost finish win32 iconv issues (#872), restored-file + entry-timestamp bugfix (r7090, #1523), always print CWD as '.' (r7097) + + +Version 0.29.0 (released 05 Sep 2003, revision 6976) +http://svn.apache.org/repos/asf/subversion/tags/0.29.0 + + User-visible changes: + * 'svn status' now streams its response. (r6913, #1426) + * 'svn status' now recurses into externals (r6913, #1428) + * new 'svnadmin verify' command to verify repository data (r6851, #1074) + * SSL changes: (r6958, #1371) + - dropped support for PEM-encoded client certs, only accept PKCS12 now. + - 'ssl-authority-files' is now a list of CA files + - no more 'ssl-client-cert-type' and 'ssl-client-key-file' variables. + * new svndumpfilter option: '--preserve-revprops' to keep props on empty revs + * mailer.py improvement: handle multiple match groups (r6940) + * remove in-repos/on-disk repository template features, till post-1.0 (r6965) + * various cleanups to the Book + * other bugfixes: switch deletion bug (r6890, #1496), status + repos-delete bug (r6913, #1469), reversion of '.' (r6953, #854). + + Developer-visible changes: + * GUI developers take note: prompting API changed (r6928, #1214) + * now compile against neon-0.24; 0.23.9 support to be dropped soon. (r6958) + * various improvements to Perl/SWIG bindings + * tree re-org: non-core utilities split into 'tools' and 'contrib' areas. + * some gen_make.py/gen-base.py improvements + * configure.in CFLAGS bugfix (r6963) + * stop calling deprecated APIs in APR, in preparation for upcoming APR-1.0. + + +Version 0.28.2 (released 29 Aug 2003, revision 6946) +http://svn.apache.org/repos/asf/subversion/tags/0.28.2 + + User-visible changes: + * MAJOR BUGFIX: revert revision 6764. + + The new history-searching code was over-stressing our use + of BerkeleyDB transactions, causing checkouts to go + twice as slow and lose all concurrent-client scalability. + + This is a temporary fix for a larger design problem. See issue + http://subversion.tigris.org/issues/show_bug.cgi?id=1499 + + +Version 0.28.1 +(released 28 Aug 2003,http://svn.apache.org/repos/asf/subversion/tags/0.28.1) + + There are no changes in this release. + It is strictly an updated release, build with the correct version + of autoconf; autconf-2.57 + + +Version 0.28.0 (released 27 August 2003, rev 6894, branches/release-0.28) +(http://svn.apache.org/repos/asf/subversion/tags/0.28) + +##################################################################### +## WARNING WARNING WARNING WARNING WARNING WARNING WARNING ## +##################################################################### +## ## +## This release makes an incompatible change to the Subversion ## +## repository filesystem schema. Repositories created with ## +## Subversion code prior to this release will unable to operate ## +## with this new code. To maintain the ability to use said ## +## repositories, you must use a version 'svnadmin dump' prior to ## +## this change to dump your repository to a dumpfile, then use ## +## this new Subversion code to create, and load your dumpfile ## +## a new repository using 'svnadmin load'. And don't forget to ## +## copy over any custom configuration/hooks from the old to the ## +## new repository. ## +## ## +##################################################################### + + Please see notes/repos_upgrade_HOWTO for documentation on migrating + pre-0.28.0 repos to 0.28.0. + + That document is also located here: + http://svn.apache.org/repos/asf/subversion/trunk/notes/repos_upgrade_HOWTO + + User-visible changes: + * fs schema change, see issue #1003 **NOTE: repos dump/load cycle required!** + * command-line options + - changed 'lsdblogs' is now 2 commands 'list-dblogs', 'list-unused-dblogs' + - removed '--only-unread' option + - new 'list-unused-dblogs' + - new '--config-dir' allows svn config to live outside ~/.subversion + - new (r6811) + - svn-status-get-specific-revision (interface to svn cat) + - svn-ediff-with-revision (run ediff w/ a file wc and a specified rev) + * fixed 'mod_dav_svn' segfault bug caused by foreign DeltaV requests (r6725) + * fixed 'svn switch' bug which could result in corrupted repo (#1316,r6746) + * fixed items now marked as 'deleted' if they no longer exist (#919,r6748) + * fixed 'merge' no longer adds file/dir if scheme differs from wc. (#1321) + * fixed Handle \r correctly when prompting on Windows. (r6792,#1307) + * 'svn merge' now 'skip's when it hits unversioned obstructions (r6810,#1425) + * fixed repos->wc of file w/ svn:keywords set caused segfault (r6818,#1473) + * fixed 'svn diff -r PREV:HEAD' failed if tmp/ exists in cwd (r6838,#1487) + + Developer-visible changes: + * database schema changed (see warning above!) (r6752,#1003,#1248,#1438) + * svn Perl bindings are ready - see swig/INSTALL + * internal changes to treat swig libraries more like normal libraries (r6761) + * improved handling of errors opening a repository over ra_svn (r6841) + + + +Version 0.27.0 (released 12 August 2003, rev 6707, branches/release-0.27.0) + + User-visible changes: + * fixed ra_svn: + - (r6588) avoid hangs due to ra_svn tunnel creation errors + - (r6696, r6697, #1465) svnserve crash due to pre-commit hook failure + * fixed 'svn log': + - (r6642, #1423) log on a deleted path over ra-dav + - (r6684, #1438) log performance bug + * fixed 'svn diff' and 'svn merge': + - (r6604, #1311) diff URL URL on files now works + - (r6668, #1142) diff comparing wc to repos branch + - (r6687, #1297) diff/merge interaction in file adds + - (r6703, #1319) merge problem with adding subtrees + - (#6607) new default ancestry-following behavior for diff, merge + * fixed 'svn status': + - (r6688, r6691, r6692, #1289) status on nodes deleted in repos + - (r6637) status now always uses "I" for ignored directories + * fixed 'svn copy': + - (r6704, #1313) copy between 2 repositories errors cleanly now + - (r6649, #1444) seg fault when copying empty dir from repos to wc + * fixed 'svn export': + - (r6652, #1461) exporting an empty directory + - (r6664, #1296) path->path exports + * fixed - gracefully handle failure to get uid on Win98 (r6695, #1470) + * fixed - avoid spurious conflicts when merging binary files (r6621, #1319) + * fixed - merge of a single file into implicit '.' (r6630, #1150) + * fixed - various Win32 innosetup improvements/fixes (r6693, r6656, #1133) + * fixed - disallow ".." in svn:externals target paths (r6639, #1449) + * fixed - use 'env' instead of hard-coded paths in scripts (r6626, #1413) + * fixed - bug in loading incremental dumpfiles (r6595) + * fixed - performance issue in svn_load_dirs.pl fixed (r6623, r6627, #1455) + * fixed - handle IPv6 addresses in URLs (r6638, r6654, #1338) + * changed - 'svn resolve' renamed to 'svn resolved' (r6597) + * changed - 'svnlook tree' takes new optional path-in-repos argument (r6583) + * changed - renamed 'svnadmin archive' to 'svnadmin lsdblogs'; offer + either all logs, or just the unused ones (r6661) + * changed - now offer full-text search in Windows documentation file (r6658) + * changed - much documentation updated, especially the book + * Many other fixes and changes, too numerous to mention individually. + + Developer-visible changes: + * fixed - many improvements to Perl and Python bindings, including some + Win32-specific improvements (r6619, r6686, r6590, r6580, r6579) + * All other important dev changes are implied by the user-visible changes. + + +Version 0.26.0 (released 24 July 2003, revision 6550, branches/release-0.26.0) + + User-visible changes: + * fixed - --parent-dir option to svnadmin load (r6436) + * fixed - 'svnlook diff' now properly displays diffs (r6408, #1241) + * fixed - 'svn cat' no longer expands values from the wrong revision. + * fixed 'svn merge': + - (r6447,#1402) -r FOO:PREV works correctly + - (r6452,#1379) no longer prints confusing no-op lines + - (r6500/6503,#1399) warn user when a tree-delta chunk can't be applied + * turn compression off to work around to mod_deflate timeouts (r6509) + + Developer-visible changes: + * cvs2svn.py: lots of bugfixing related to branch/tag support + * diff code refactored to allow use by other tools (r6407) + * make 'svn export' set commit-timestamps (not ready yet) (r6420) + * fixed - memory leaks in libsvn_ra_dav commits (r6422) + * fixed - cvs2svn.py handles branch files rooted in dead revs (r6482,#1417) + * fixed - new lines now detected in svn:author property (r6497,#1401) + * fixed - svn_load_dirs works w/ absolute paths (r6507, Debian bug #187331) + * changed - build infrastructure now supports Perl SWIG bindings (r6441) + * removed - PORTING document no longer necessary (r6472) + + +Version 0.25.0 (released 11 July 2003, revision 6394, branches/release-0.25.0) + + User-visible changes: + * command line options: + - new --force option for svn export (r6327,#1296) + - new --force-log for commit, copy, delete, import, mkdir, move (r6294) + - no longer need --force for commit + * commands + - new - svnadmin archive (r6310) + - changed - svn import syntax now 'svn import [PATH] URL' (r6288,#933,#735) + - fixed - Search PATH for external diff commands (r6373) + - fixed - 'svn switch' memory bug (r6296) + - fixed - 'svn mkdir' coredump (r6388,#1369) + * python bindings now in -tools rpm for Mandrake 9.1 (r6374) + * allow parent-into-child copies, provided they are not WC->WC. (r6348,#1367) + * fixed - Apache module installation order (r6382-6,#1381) + * now require apache 2.0.47 (and apr 0.9.4) + * fix 2 commit leaks + * fix mod_dav_svn path-escaping bug + + Developer-visible changes: + * Win32 build system + - new - .vcproj files for svn_config project and APR (r6311) + - fixed - SWIG bindings for Win32 (r6304) + - vcproj generator now works (r6316) + - swig's generated .c files now dependent on headers in .i files (r6379) + - refactored code common to dsp & vcproj into gen_win.py (r6328) + * fixed + - SEGFAULTs in SWIG bindings (r6339) + - potential SEGFAULTs in 'REPORT vcc' backward-compatibility code (r6377) + - mod_dav_svn's autoversioning failure on PUT (r6312) + - 'svn switch' memory bug (r6296) + * changed - mailer.py now uses svn_repos_replay() + + +Version 0.24.2 (released 18 June 2003, revision 6284, branches/release-0.24.2) + + User-visible changes: + * fix 'svn export' potential segfault + * fix occasional diff test failures + * fix 'svnadmin dump' memory hog + * fix new-dir-with-spaces bug + + Developer-visible changes: + * none + + +Version 0.24.1 (released 16 June 2003, revision 6249, branches/release-0.24.1) + + User-visible changes: + * Fix bug in 'svn log'. + + Developer-visible changes: + * none + + +Version 0.24.0 (released 15 June 2003, revision 6234, branches/release-0.24.0) + + User-visible changes: + * new 'svn diff [--old OLD] [--new NEW]' syntax (#1142) + * new --relocate option for svn switch (#951) + * new --version option for svnadmin and svnlook + * new path-based authorization module for apache + * make 'svn checkout' and not just 'svn update' resume a checkout + * .svn directories now hidden on Windows + * config variable 'store-password = no' now actually works + * fix 'svn merge --dry-run' + * fix 'properties hanging after a rename' issue (#1303) + * fix odd behavior of 'svn up -r PREV filename' (#1304) + * fix delete-tracking bug (#1348) + * fix dump and load corner cases (#1290) + * ra_dav server more resilient for foreign deltaV clients + * numerous ra_svn changes - must update clients and servers + * fix export over ra_svn (#1318) + * fix ra_svn error transmission bug (#1146) + * fix ra_svn corruption in tunnel mode (#1145) + * make svnserve multi-threaded on non-fork platforms (now works on Windows) + * remove svnserve -F and -S options + * various memory use improvements (#1323) + * various performance improvements for all protocols + * various performance improvements for 'svnadmin dump' and svnlook + * various subversion book updates (you have read the book right?) + * more cvs2svn.py improvements (and more to follow) + * new debugging script normalize-dump.py to normalize dump output + + Developer-visible changes: + * path-based editor drivers + * no more RA->do_checkout() + * update python and java bindings + * various windows build fixes + + +Version 0.23.0 (released 16 May 2003, revision 5962, branches/release-0.23.0) + + User-visible changes: + * 'svn cat' now performs keyword expansion (#399) + * 'svn export' keyword expansion fixed + * checkouts are now restartable (#730) + * ssh ra_svn tunnel agent specified with svn+ssh://hostname/path syntax. + * remove dependency on external diff program + * don't error out early on unversioned files (#774) + * fix commands where REPORT fails if item isn't in HEAD (#891) + * updates now receive checksums like checkouts (#1101) + * 'svn revert dir' now resets property timestamp (#1163) + * fix instances of client showing help instead of error message (#1265) + * fix incorrect path in 'not a working copy' error messages (#1291) + * fix cvs2svn.py file added on branch problem (#1302) + * fix various vc-svn.el problems (#1257, #1268) + * fix various psvn problems (#1270) + * various Win32 build fixes + + Developer-visible changes: + * fix various gcc 3.3 warnings (#1031) + * fix various memory errors/leaks + * remove java/jni bindings + + +Version 0.22.2 (released 13 May 2003, revision 5918, branches/release-0.22.2) + + User-visible changes: + * fix Win32 build + * properly handle on-disk template errors + * fix bogus uuid bug in cvs2svn.py + + Developer-visible changes: + * none + + +Version 0.22.1 (released 9 May 2003, revision 5874, branches/release-0.22.1) + + User-visible changes: + * fix shared library installation problem + * update cvs2svn.py script + + Developer-visible changes: + * none + + +Version 0.22.0 (released 7 May 2003, revision 5842, branches/release-0.22) + + User-visible changes: + * svn diff -r BASE:HEAD and other edge cases fixed (#977) + * svn diff and merge now have --ignore-ancestry option (#1034) + * svn ci -N DIR no longer errors during post-commit (#1239) + * ra_dav now optional (#617) + * update vn-svn.el (#1250, #1253) + * improvements to svn_load_dirs.pl (#1223, #1215) + * misc ra_svn bug fixes and protocol change + * log-encoding option now properly only applied to logs + * fix mmap failures on HP-UX + * fix some client memory leaks + + Developer-visible changes: + * finish transition to new xml prop namespaces for mod_dav_svn (#840) + * minimize full tree locks and number of system calls (#1245) + * auto-generated .dsp files (#850) + * fix ETag of directory (#1251) + * added export editor (#1230) + + +Version 0.21.0 (released 15 Apr 2003, revision 5639, branches/release-0.21) + + User-visible changes: + * SSL client and server certificate verification + * authentication info now stored in ~/.subversion/auth/ + * svn diff on a copied file only shows local mods, not the whole file + * svn propget now takes a --strict option to control output + * svnadmin load now takes a --parent-dir option + * added the new 'svndumpfilter' program + * svnlook now has 'cat', 'propget', and 'proplist' commands to enable + viewing this information on transactions + * 'svn copy' from another repository now adds without history + * tag/branch conversion disabled in cvs2svn until it gets fixed + * the 'anonymous' user is no longer used; we simply avoid attaching an + author property when an author is not available + * improvements to ignored-file handling + * Python ConfigParser-style variable expansion for config file (#1165) + + Developer-visible changes: + * introduced the svn_filesize_t type (#639) + * realmstring added to the svn_auth framework + * the "result checksum" moved to the editor.close_file function + * more checksumming here and there + * initial work to enable binary properties via ra_dav + * initial, internal support for compressed streams + * test framework shifting to exception-based failure recording (#1193) + * improved options and handling in the C test framework + * java and python binding work + * libsvn_auth folded into libsvn_subr + * bug fixes: 'svnadmin load' parse bug; ra_svn crashes (#1160); 'svn + log' on a switched wc (#1108); 'svn ci -N' on named files (#1195) + + +Version 0.20.1 (released 26 Mar 2003, revision 5467, branches/release-0.20.1) + + User-visible changes: + * fix svnadmin load bug so that property deletions actually occur + * fix checksum compatibility issue for older repositories + + Developer-visible changes: + * none + + +Version 0.20.0 (released 20 Mar 2003, revision 5410, branches/release-0.20) + + User-visible changes: + * new compatibility rule: require only that each interim release be + compatible with the one before it (see HACKING) + * ra_svn is still new so above rule doesn't yet apply + (i.e. 0.20 over ra_svn is NOT compatible with previous releases) + * merge infers the target path (see book chapters 4 & 8) + * merge continues in presence of missing target file + * merge's add notifications are no longer duplicated + * commands can be safely interrupted (Ctrl-C) + * --encoding global default in ~/.subversion/config + * new option --editor-cmd + * begin multi-release transition to escape binary properties over DAV + * misc performance improvements + + Developer-visible changes: + * RA vtable functions take pool argument + * svn-config --includes path fixed + * uuid at creation now complete + * start having test failures throw exceptions rather than return errors + * test suite option --cleanup with --verbose being default mode + * continued diff library development + * minor revprop hook changes + * bug fixes: no diff on binary files (#1019), consistent error messages + (#1181), version numbers in hook scripts (#1182), win98 codepage (#1186) + + +Version 0.19.1 (released 12 Mar 2003, revision 5303, branches/release-0.19.1) + + User-visible changes: + * fix svnserve tunnel mode pipe close bug + + Developer-visible changes: + * none + + +Version 0.19.0 (released 10 Mar 2003, revision 5262, branches/release-0.19) + + User-visible changes: + * svn ls works on wc paths (#1130) + * new cvs2svn.py features and bug fixes (1105) + * new svnlook subcommand 'uuid' + * new svnadmin create option '--bdb-txn-nosync' (use with care) + * fix svnserve help output + * SVN_EDITOR now overrides svn-editor in ~/.subversion/config + * miscellaneous performance improvements (memory and speed) + * more work on the Book + + Developer-visible changes: + * start implementing cancellation of long-running functions + * misc windows build fixes and features (DSP generator) + * -W and -P options to stress.pl + * start adding support for multiple fs backends + * work on bindings and bindings build system (#1132, #1149) + * bug fixes: ra_dav import/checkout memory usage (#995), control chars + in commit messages (#1025), svn merge memory usage (#1069, #1077), + pre-existing ~/.subversion (#1121), keyword expansion (#1151), line + number in config error message (#1157), svn-tunnel-agent in [default] + (#1158), RA->close RIP (#1164), config-test non-source (#1172) + + +Version 0.18.1 (released 26 Feb 2003, revision 5118, branches/release-0.18.1) + + User-visible changes: + * editor environment variables no longer incorrectly required + * 'svn help import' now displays correct usage + * fix crashes in the internal diff library and ra_dav + * fix Win9x/Me console issue + * cvs2svn.py api fix + * hot_backup.py now correctly removes old backups + + Developer-visible changes: + * various rpm package fixes + + +Version 0.18.0 (released 19 Feb 2003, revision 4968, branches/release-0.18) + + User-visible changes: + * renamed the [default] section to [global] in the servers config file + * compression option is now http-compression and lives in servers file + * use internal diff by default rather than external program (#405 in progress) + * symlinked hook scripts now run + * read-only access flag (-R) for svnserve + * quiet flag (--quiet) for svnadmin dump + * --ignore-uuid and --force-uuid for svnadmin load + * miscellaneous performance improvements + * more work on the Book + + Developer-visible changes: + * new authentication library libsvn_auth (#724) + * new bdb table uuids + * client context object in libsvn_client + * more work on java and other language bindings + * test framework now has a quiet option (-q) + * miscellaneous small code cleanups + * bug fixes: more valgrind memory bugs, apr xlate i18n mess (#872), + non-existent URL checkout (#946), props on to-be-deleted files (#1066), + ra_svn move/copy (#1084), eol translation (#1085), ra_svn + checksumming (#1099), cat command corrupt output (#1104), cvs2svn + memory consumption (#1107), merge of property add (#1109), + '..' relative path (#1111), commit/cleanup/diff3 (#1119), + .svn/entries checksum (#1120), svn commit in / (#1122), + status on uncontrolled directory (#1124), commit message eol + characters (#1126), cat -r PREV (#1134), ra_dav wcprops (#1136) + split XML cdata/attribute encoding (#1118) + + +Version 0.17.1 (released 22 Jan 2003, revision 4503, branches/0.17.1) + + User-visible changes: + * changed non-baseline build version number display. + * compatibility change: make sure old clients can talk to newest servers. + * some changes to the Book + + Developer-visible changes: + * dumper/loader now use checksums (#1102) + * miscellaneous small code cleanups + * bug fixes: eol-style timestamp changes (#1086), valgrind mem bug, + better checksum error reporting, + + +Version 0.17.0 (released 20 Jan 2003, revision 4468, branches/0.17) + + User-visible changes: + * 'svn add' is now recursive by default, -N to disable (#1073) + * new 'svnversion' program summarizes mixed-revs of a working copy + * huge improvements to the mailer.py tool + * more work on the Book and man page + * default global-ignores now built-in, new runtime-config file commented out + + Developer-visible changes: + * checksums, checksums everywhere (issues #649, #689): + - filesystem stores them, and verifies them when reading/writing + - working copy stores them, and verifies them when reading/writing + - checksums transferred both ways over network, RA layers verify them + * finish draft of internal diff/diff3 library -- ready for testing/optimizing + * more utf8<->apr conversion work (#872) + * more work on swig/python and ruby bindings + * improvements to win32-innosetup package + * 'svnserve' now has an official IANA-assigned portnumber. + * mod_dav_svn now only sends/understands new xml prop namespaces (#840) + * bug fixes: stop needless fs rep data copies (#1067), wc auth + caching bugs (#1064), use APR_BUFFERED to open files (#1071), lots + of wc 'missing dir' edge-case bugs (#962), prevent wc from + receiving '.svn' (#1068), don't commit symlinks (#1081), better + diff labels (#1080), better fulltext tmpfile names in conflicts (#1079), + prevent ra_dav from deleting out-of-date items (#1017), segfault (#1092), + don't attempt checksum on missing tmp textbase (#1091), allow diffs + during update again (yikes!) + + +Version 0.16.1 (released 6 Jan 2003, revision 4276) + + User-visible changes: + * ra_svn network layer (apache alternative) now tested & ssh-tunnelable + * new (experimental) mod_dav_svn autoversioning feature (SVNAutoversioning) + * reorganization of the ~/.subversion/ run-time config files. + * more entry caching: approx. 3x speedup on checkouts & updates. + * option rename: --non-recursive instead of --nonrecursive + * option rename: --no-diff-deleted instead of --no-diff-on-delete + * new 'svn log --quiet' + * new 'svn diff --no-diff-deleted' + * fix keyword expansion behaviors ($keyword:$ / $keyword$ / $keyword: $) + * handle win32 non-ascii config-file paths (#968, #1048, part of #872) + + Developer-visible changes: + * most public header files now using doxygen markup + * new (untested) internal difflib (#405) + * neon debugging now tweakable via run-time config file + * more progress on Subversion Book rewrite. + * new ./configure --with-diffutils + * begin work on client/server checksums (#649) + * regression tests now depend on svnadmin dump/load + * lose src_err field of svn_error_t + * many fs function renames: begins fs database back-end abstraction. + * new libsvn_repos prop-validating wrappers + * lots of work on build-system dependency graph generation (for SWIG building) + * swig binding work: + - python svn calls can now run as independent threads + - new java-binding build system + - improved swig building features: --prefix, LDFLAGS behaviors + * many, many bug fixes: wc->repos copies (#1029), #943 followup + (#1023), copies of copies (#830), 'svn resolve' cleans up entries + file (#1021), prop merging (#1012), segfault fixes (#1027, #1055), + autoconf 2.5X (#886), O(1) copies (#717), new 'failed revert' + signal (#714), detect missing schedule-add conflicts (#899, #863), + begin dav namespace switchover (#840), status bugs, url auth + inference (#1038), log bug (#1028), newline prompt (#1039), + svnadmin errorchecking, url syntax errors (#1057, #1058), apr/utf8 + work (start #872), and many more. + + +Version 0.16 (released 4 Dec 2002, revision 3987) + + User-visible changes: + * new 'svn cat' subcommand + * new --revprop flag to access revision props, -r for versioned props (#943) + * new "compression" runtime option in ~/.subversion/config + * svnadmin/svnlook now use help system, and some subcommands deleted or moved. + * tool changes: + - new svnshell.py tool + - new mirror_dir_through_svn.cgi script + - new svn_load_dirs.pl features + - updates to vc-svn.el + * --message-encoding is now just --encoding, and affects svn: propvals too. + * major rewrites of chapters 3, 4, 5 of the Subversion Book. + + Developer-visible changes: + * new network layer, libsvn_ra_svn! still experimental. + * all svn_error_t's now allocated in subpool of global pool. + * reorganize svnlook/svnadmin subcommands & option-parsing (#540, #915, #910) + * all log messages and svn: props now stored as UTF8/LF endings (#896) + * huge cleanup/reorg of all svn_path_* routines + * svn_client_status sends feedback, distinguishes unversioned vs. ignored + * improvements to swig typemappings and build processes + * fixes to pool cleanup handlers + * begin abstraction of gen_make.py + * entry-caching improvements + * stop using global apr_xlate objects + * win32-innosetup code added to packages/ + * new work on ruby bindings and swig-java bindings + * many, many bug fixes: various small coredumps, svn_error_t leaks, + copy props correctly (#976), copy executable bits correctly (#982), + test-system fix (#882), accidentally imported tmpfile (#964), + ra_local checkout memleak (#985), accidental wc deletion (#988), + better text vs. binary detection (#994), dav log-report error + handling, bad 'svn switch' dav caching (#1000), don't call NULL + callbacks (#1005), bogus switch feedback (#1007), eol-style file + corruption (#920), getdate.y fix (#1009), ra_local error reporting (#900), + start of work on issues #830 and #869. + + +Version 0.15 (released 7 Nov 2002, revision 3687) + + User-visible changes: + * New 'S' indicator in 'svn status' shows switched subdirs + * New --dry-run option added for 'svn merge' (issue #953) + * Fix 'svn update .' to handle svn:externals correctly + * Memory usage of 'svn import' reduced (issue #860) + * Allow 'svn revert' on missing directories scheduled for deletion + * Assorted bug fixes in several exciting flavors + * Documentation improvements + + Developer-visible changes: + * #911 (apr and apr-util version at build time) + * Fixed issues #851, #894, + * Testing scripts accept --url=URL and BASE_URL=URL + * Issue #881 (--enable-all-static) + * Delta editors all converted to new-style, and editor composition is gone + * Improve libsvn_wc wcprop handling (issue #806) + * SWIG binding improvements + * Various pool usage improvements + + +Version 0.14.5 [Alpha Interim 5] (released 30 Oct 2002, revision 3578) + + User-visible changes: + * allow --incremental option for 'svn log' xml output + + Developer-visible changes: + * autoconf bugfix for berkeley-db detection + * clean up property interface mess (part of #806) + * dish.sh bugfix: build the new docbook docs correctly + * python tests now log commands + * gen-make.py now assumes 'build.conf' + + +Version 0.14.4 [Alpha Interim 4] (released 29 Oct 2002, revision 3553) + + User-visible changes: + * new working-copy entry-caching: speeds many ops up to 5x (#749) + * new 'svnadmin recover', instead of db_recover + * client can now view & change server-side revision props (e.g. log messages) + * new --non-interactive switch for commandline client + * new --incremental option to 'svn log' + * new -r {date} syntax for specifying dated revs; works over network too. + * automatically set svn:executable prop when adding or importing (#870) + * initial $EDITOR text now ignores all log data below special token + * consistify behavior of text & prop columns in 'svn status' output. + * .svn/auth/* files now chmod 700, to stop scaring people. :-) + * improved labels in 'svn diff' output (#936) + * run-time adjustable neon timeout in newly renamed 'servers' config file + * big improvements to cvs2svn script: bugfixes and basic branch/tag support + * new python access-control hook script + * no more implicit dot-target for 'svn propedit' or 'svn propset' (#924) + * Win32 improvements: + - use system-wide config-file/registry + - run-time configurable diff/diff3 binary locations (#668) + * remove obsolete --xml-file support + * Handbook is now ported to Docbook, 2 new chapters. + + Developer-visible changes: + * abstracted option/help-parsing code, now shared between svn and svnadmin + * require apache 2.0.42 + * use neon 0.23.5: fix XML entity derefs, SSL server certs, HP-UX build, etc. + * support Berkeley DB 4.0 *or* 4.1 + * many SWIG binding improvements: + - better overall coverage of apr and libsvn_* library symbols + - new 'make swig-py-ext' and 'make install-swig-py-ext' targets + * finish conversion of all editor/drivers to "new" style (#737) + * removed xml-delta editors and editor drivers and related tests + * new predicate-logic system added to automated-test system ("skip" support) + * more work on mailer.py + * no more lost commit messages (#761) + * eradication of misused stringbufs, obsolete code removal (#909) + * mem-leak fixes in libsvn_fs (#860) + * improved atomicity of working-file translations (#914) + * improve ./configure --help output (#949) + * MANY bugfixes, especially for entry-locks (#931, #932, #847, #938), + merges (#880, ), auth storage (#934); also #921 (svnadmin + segfault), #907 (xml quoting), #918 (post-commit processing), #935 + (path canonicalization), #779 (diff errors) + + +Version 0.14.3 [Alpha Interim 3] (released 20 Sept 2002, revision 3200) + + User-visible changes: + * new ~/.subversion/config file + * new $Id$ keyword + * new client --no-auth-cache option + * empty values in the Windows Registry are no longer ignored (issue #671) + * report details of repository start-commit or pre-commit hook errors + * fix locking behaviour when using current directory as a target + * updated man page + * new front-page logo. :-) + + Developer-visible changes: + * continuing work on python SWIG bindings + * continuing work on new access-baton system for libsvn_wc + * upgrade to neon 0.23.4 to fix Windows build issues and seg faults + * add XFAIL to the C testing framework + * prevent setting of certain svn: props on incorrect file types + * cleanup libsvn_subr's path library behavior + * new 'fast-clean' vs. 'clean' Makefile targets + * various bugfixes, tweaks, cleanups. + + +Version 0.14.2 [Alpha Interim 2] (released 22 Aug 2002, revision 3033) + + User-visible changes: + * fs schema change, see issue #842. **NOTE: repos dump/load cycle required!** + * new 'svn ls -R' option + * new status code `~', for type changes + * add --username and --password options to 'svn ls' + * new script tools/client-side/svn_all_diffs.pl + * new script tools/examples/blame.py (draft) + + Developer-visible changes: + * test suite now does XFAIL and XPASS + * test suite over DAV now uses SVNParentPath, no longer depends on symlinks + * DAV tests now work on Windows + * upgrade to neon 0.22.0 + * 'make install' notices the $(DESTDIR) parameter + * new dav prop namespaces, but old still sent for compat; see issue #840 + * error code space reorganized, see issue #702 + * many cleanups to path handling + * more use of access batons in libsvn_wc, see issue #749 + * working props now stored with ".svn-work" extension, see issue #618 + * the usual round of bug fixes, new regression tests, etc + + +Version 0.14.1 [Alpha Interim 1] (released 9 August 2002, revision 2927) + + User-visible changes: + * show copy-ancestry in 'svn log -v' + * 'svn co' can take multiple URLs now + * new 'svn ls' command + * new 'svn st --no-ignore' option + * new 'svn --version --quiet' option + * more conservative 'svn help' usage error-message + * more graceful degradation from charset conversion failure + * standardize policy of -q switch behavior + * less intimidating error output + * new SVNParentPath directive for mod_dav_svn s + * svnlook now correctly displays copied subtrees + * Handbook: additions, tweaks, cleanups, and new French Translation :-) + * svn_load_dirs.pl: auto propset on files matching specified regex, bug fixes + + Developer-visible changes: + * integrated the delta-combiner! (issue #531) + * integration of libsvn_wc-baton-locking branch (issue #749) + * new "skip-deltas" added to delta-combiner + * properly URI-encode/decode path components throughout our code + * RA->do_diff() made independent from RA->do_switch(). + * stricter setting/parsing of svn:mime-type property in client and server. + * new 'install-static' make target + * extend SWIG bindings to libsvn_wc and libsvn_client + * BerkeleyDB usage tweaking: in preparation for auto-recovery features. + * work on #850 (.dsp generator) + * Better support for incremental dumps (see revision 2920) + * started fs branch work on #842 (copyID inheritance), #830 (copies of + copies), #790 (copy table uses txnID), #815 (custom sorting) + * numerous bugfixes: #709 (better error handling), #813/814 + (apr_filepath_merge), #685 (showing dir propdiffs), OS X dumper + bugfix, #561 (property conflict detection), mod_dav_svn path bugs, + svn_wc_status() bugs, path canonicalization bugs, #816 (svn log -r), + #843 (URL keyword), #846 (kind-change replacement), #809 ($EDITOR dir), + #855 (module updates not cooperating with new wc access batons), + improvements to test suite sensitivity, + + +Version 0.14.0 [Alpha] (released 23 July 2002, revision 2667) + + User-visible changes: + * finally some documentation: The Subversion Handbook + * i18n support for paths, prop names, and log messages; (not on Win32 yet) + * support for URI-escaped paths + * "-R" is now short for --recursive, and "-N" replaces "-n" + * add the -R option to 'svn info' and 'svn resolve' + * new syntax for 'svn switch' and 'svn co' + * new 'svn-config' file installed + * new commit-access-control.pl utility (feature #775) + * new vc-svn.el, first pass at Emacs VC support for Subversion + * lots of work on svn_load_dirs.pl (provides vendor-branch-like features) + * new --message-encoding option for logfiles given by -F + * support win32 drive-letters in file:/// urls + * improved date output syntax: ISO-8601 prefix, then human-friendly suffix + * the usual round of bug fixes + + Developer-visible changes: + * UTF-8 changes + - all libraries now assume UTF-8 input paths and log msgs + - many apr calls are now abstracted into new svn_io_* wrappers + * fs schema change + - cache each revision's changed-paths in a new 'changes' table + - another repository dump/load is required + * a number of fs-dumper bugfixes and redesigns + * test suite is now all python, so it can run on win32 + * reduce huge memory consumption of mod_dav_svn during checkouts + * memory optimizations for prop-reading and 'svn diff' + * bugfixes for commit-email.pl and tweak-log.cgi + * lots of branch work on the delta-combiner and on libsvn_wc rewrite + * numerous bugfixes: 'svn merge .' bug (#748), bug #764, two new + ghudson-dirversioning bugs, #756, #675, #783, #796, wc-root bugs, + #799, #800, #797, directory-removal bugs (#611, #687) + + +Version 0.13.2 [Pre-Alpha] (released 28 June 2002, revision 2376) + + User-visible changes: + * fixed various buggy commandline outputs + * allow global/local config-files on win32 + * prevent overwrites with 'svn cp URL URL' + * improvements to svn_load_dirs.pl + * mod_dav_svn can generate xml output for directory GETs + * new svnadmin(1) man page + + Developer-visible changes: + * finished notification callback system, no more buggy output + * fs-changes: + - revisions table nothing but an index to txns table + - branch work-in-progress: new 'changes' table to store changed paths + * more work on svn_time_* funcs and formats (moving towards ISO8601) + * property reversion bugs fixed, dumper bug fixed + * add version number to svndiff database storage + * new regression tests for 'svn merge' + * fix 'svn diff -rX:Y' server bug + * fix bugs in python test system + * bring win32 build up-to-date, get most python tests working on win32 + + +Version 0.13.1 [Pre-Alpha] (released 20 June 2002, revision 2291) + + User-visible changes: + * "modules" are now implemented + * new 'svn export' command + * 'svn log' now traverses copy history and can print changed paths + * 'svn merge' now (temporarily) only merges into '.' + * 'svnadmin lscr' now traverses copy history + * changes to the 'svn:executable' prop take effect immediately now + * server is more tolerant of wc's with old-style version resource URLs + * new Handbook started + * commit-email.pl fixes/improvements -- now shows prop mods and copy history + * bug fixes to cp, rm, merge, revert, admin dump and load, svnlook + + Developer-visible changes: + * headers now install in subdir and libs are named libsvn_FOO-1.so + * improvements to the Python test suite + * delta combiner implemented (unused for now, though) + * Python SWIG binding improvements: ability to write an editor in Python + * new example: tools/examples/svnlook.py + * start moving libsvn_client to new notification system (no composed editors!) + * upgrade to neon 0.21.2, fixing deflated communication with apache + * Moved Berkeley-specific code to libsvn_fs/bdb/, skels into libsvn_fs/util/ + * changes to the RPM packaging + + +Version 0.13.0 [Pre-Alpha] (released 10 June 2002, revision 2140) + + User-visible changes: + * repositories have a new database schema; existing ones must be upgraded! + - new svnadmin 'dump'/'load' commands to migrate repositories + - read http://svn.apache.org/repos/asf/subversion/trunk/notes/repos_upgrade_HOWTO + + Developer-visible changes: + * complete rewrite of filesystem schema! + - skels are abstracted away, opening the door to SQL backends + - node-ids now have copy IDs + * huge progress on module system [only checkouts work at the moment] + * massive conversion of stringbufs to char* in our public APIs + * vsn-rsc-urls are now based on created-rev/path instead of fs_id_t's. + * reinstate 'deleted' flag on entries, to ensure accurate update reports + * dir_delta learns how to send copy history + - svnlook no longer sends 10MB emails when we make a branch + - dumpfiles get much smaller + * memory consumption reduced via new apr-pool code that reuses/frees mem + * client can now parse ISO-8601 timestamps (start of issue 614) + * added script for stress-testing concurrent repository access + * auto-locate apache's apr libraries at build-time + * beginnings of ra_pipe library + * progress on delta combiner code + * many memleaks fixed, thanks to valgrind! + * upgrade to newest neon, allow deflated communication with apache + * many bugfixes to merge, switch, checkout, rm; tackling of issues 704, + 705, 698, 711, 713, 721, 718 and many others + + +Version 0.12.0 (released 3 May 2002, revision 1868) + + User-visible changes: + * 'svn diff' can now compare two arbitrary URLs + * 'svn diff' now displays property changes + * 'svn rm' requires --force for unversioned and/or modified items + * 'svn rm' immediately removes files & uncommitted dirs + * 'svn mv' for WC->WC behaves like 'svn rm' with respect to the source + * checkouts, updates, switches now print received revision on final line. + * new 'svn info' command prints information about a versioned resource. + * switch to 2-part conflict markers (diff3 -E) instead of 3-part (diff3 -A) + * new bash programmable completion file + * file's executable bit can be versioned (svn:executable prop) + * commits and imports now support --nonrecursive option + * new --xml option for 'svn log' + * new 'svnadmin dump' command + + Developer-visible changes: + * updates correctly deal with disjoint urls. + * libsvn_wc now checksums text-bases, to detect working copy corruption + * cached wcprops (vsn-rsc-urls) now auto-regenerate if invalid + * python testsuite now runs on Win32. + * new switch_tests.py added to testsuite + * NEW internalized diff/diff3 library. Not yet integrated/tested. + * dir_delta sends entry props; pipe-editor removed. + * no more expat/ tree; use apr-util's expat instead. + * fs deltificaton happens outside commit process, using fewer db locks + * privatize svn_fs_id_t structure + * start abstracting skels out of libsvn_fs + * new docs: secure coding tips, quickref card + * memory bugfixes for import/commit/mass removals + * many bugfixes: issues 644, 646, 691, 693, 694, 543, 684 + + +Version 0.11.1 (released 12 April 2002, revision 1692, branches/0.11.0) + + User-visible changes: + * completion of 'svn merge' (issue 504) + * added SVNReposName directive to mod_dav_svn + * insist on a diff binary that supports "-u" + * fix and unify pop-up $EDITOR behaviors (issues 638, 633, 615) + + Developer-visible changes: + * finish rewrite of commit system to handle disjoint urls (issue 575) + * finish proxy support via config files (esp. on win32) (issue 579) + * fix svn_ra_dav__get_baseline_info and related bugs (issue 581) + * reorganization of libsvn_wc header files & API + * new getopt_tests.py to test commandline option processing + * 'make check' now more portable -- tests invoked via python, not sh + * miscellaneous bugfixes in imports, svndiff, db linkage. + + +Version 0.11.0 (unreleased) + + +Version 0.10.2 (released 25 Mar 2002, revision 1587) + + User-visible changes: + * new ~/.subversion configuration directory + * proxy support via ~/.subversion/proxies file + + Developer-visible changes: + * rewrite of client-side commit process partially done + * beginnings of 'svn merge' + * mod_dav_svn now generates "streamy" report responses + * stringbuf cleanups and bugfixes + * interface to svn_wc_entry_t cleaned up + * tweaks to build system and freebsd port + * miscellaneous bugfixes in path escaping, pool usage, hp-ux compilation + + +Version 0.10.1 (released 17 Mar 2002, revision 1537) + + User-visible changes: + * New --targets command-line option for some commands. + * conflicts now create conflict-markers in files, and 3 fulltext backups. + * new 'svn resolve' command removes conflicted state (by removing backups) + + Developer-visible changes: + * no more dependency on 'patch'; only on GNU diff3 and some version of 'diff' + * complete rewrite of svn_wc_entry_t interface + * begin abstracting svn_fs API by hiding implementation details + * consolidate RA layer callbacks + * start work on commit-driver rewrite + * start work on ~/.subversion/ configuration directory, and proxy support + * move a lot of svn_wc.h into private wc.h + * bugfixes relating to commits, network prop xfers, 'svn log', 'svn co -q' + * major deletion bug fixed + (see email WARNING: + http://subversion.tigris.org/servlets/ReadMsg?msgId=64442&listName=dev) + + +Version 0.10.0 (released 08 Mar 2002, revision 1467) + + User-visible changes: + * fewer out-of-memory errors: (see "memory consumption" below) + * clearer user errors: + - detailed marshalling of server errors to client + - better errors from ra_dav + - better commandline-client-specific error messages + * 'svn log' now works on single paths correctly + * show locked directories in 'svn status' + * 'svnadmin lstxns' improvements, and new --long switch + * commits show "Replacing" instead of "Deleting/Adding" (#571) + * commits show progress on postfix txdeltas. + * WARNING: existing repositories need to be upgraded; + read tools/enable-dupkeys.sh. + + Developer-visible changes: + * reduced memory consumption + - new Editor interface that manages pools automatically + - conversion of most existing editors to new system + - have libsvn_fs write data to DB streamily + - reduce DB logfile growth via 'duplicate keys' + - stop using one pool for post-commit processing + - stop using one pool for sending all textdeltas + - many, many other pool-usage improvements in libsvn_wc, ra_dav, etc. + * start of work on 'svn merge": issue 504, and diff3 integration + * start of work on disjoint-url detection: issue 575 + * start removing stringbuf path library funcs; use new const char * funcs + * better python 2.X detection in test suite + * svnlook uses single tempdir + * build system evolution + - upgrade to neon 0.19.[2-3] + - lots of work on FreeBSD port + * many small bugfixes: + - propedit, file merges, revert, dir_delta, keywords + - memory leaks in 'svn add', 'svn import/commit', and svnlook + - date-parsing and readonly bugs + + +Version 0.9 (released 15 Feb 2002, revision 1302) + + User-visible changes: + * 'svn switch', for switching part of a working copy to a branch + * 'svn status -v' now shows created-rev and last-author info + * 'svn help ' now shows proper switches + * if no log message passed to commit, $EDITOR pops up + * greatly improved/re-organized README, INSTALL, and HACKING docs + * big progress on cvs2svn repository converter + * faster retrieval of old revisions: turn off fs directory deltification + * fixed broken behaviors in 'svn diff' and 'svn log' + + Developer-visible changes: + * new fs code for detecting differences and relatedness + * new cancellation editor, for event-driven users of libsvn_client + * make .svn/ area readonly + * continued development of ruby, java, and python (swig) bindings + * new config-file parser + * code reorganization and cleanup + - huge conversion of svn_stringbuf_t --> char * + - standardized on commit_info return structure + - no more 'path styles' in path library + - rewrite bootstrapping code for python test framework + - rewrite commandline app's help-system and alias-system + - feedback table replaced with notfication callback + - rewrite sorting of hashes + - svnadmin internal rewrite + - faster post-update processing + - using SVN_ERR macros where they weren't + - new svn_client_revision_t mechanism + - txdelta windows are readonly now + - pool debugging code moved to APR + - various pool-usage fixes + * build system evolution + - apr-util now required + - upgrade to neon 0.18.5 + - much apr m4 macro churn + - win32 updates, no longer needs precompiled neon + - 'make check' when builddir != srcdir + * fixes for many issues, including #624, 627, 580, 598, 591, + 607. 609, 590, 565 + + +[Versions 0.8 and older are only brief summaries] + +Version 0.8 (released 15 Jan 2002, revision 909) + + * newline conversion and keyword substitution (#524) + * rewrite ra_local commit system to commit against HEAD (#463) + * mod_dav_svn sends svndiffs now (#518) + * code migration from libsvn_fs to libsvn_repos (#428) + + +Version 0.7 (released 03 Dec 2001, revision 587) + + * 'svn cp/mv' completed: + - can copy from wc/repos to wc/repos + - This how we create branches/tags + * 'svn mkdir' [WC_PATH|REPOS_URL] + * 'svn delete' [REPOS_URL] + + +Version 0.6 (released 12 Nov 2001, revision 444) + + * 'svn log' + * 'svn cp/mv' from wc to wc + + +Milestones M4/M5 (released 19 Oct 2001, revision 271) + + * network layer bugfixes + * filesystem deltification + + +Milestone M3 (released 30 Aug 2001, revision 1) + + * self-hosting begins, all history left behind in CVS repository. + + +Milestone M2 (released 15 May 2001, from CVS, "milestone-2" tag) + + * filesystem library (libsvn_fs) + * network layer (libsvn_ra_dav and mod_dav_svn) + + +Milestone M1 (released 20 Oct 2000, from CVS, "milestone-1" tag) + + * working-copy library (libsvn_wc), using XML files + + +Birth (05 June 2000) + + * CVS repository created. diff --git a/COMMITTERS b/COMMITTERS new file mode 100644 index 000000000000..f0a8739f8b61 --- /dev/null +++ b/COMMITTERS @@ -0,0 +1,234 @@ +The following people have commit access to the Subversion sources. +Note that this is not a full list of Subversion's authors, however -- +for that, you'd need to look over the log messages to see all the +patch contributors. + +If you have a question or comment, it's probably best to mail +dev@subversion.apache.org, rather than mailing any of these people +directly. + +Blanket commit access: + + jimb Jim Blandy + sussman Ben Collins-Sussman + kfogel Karl Fogel + gstein Greg Stein + brane Branko Čibej + jorton Joe Orton + ghudson Greg Hudson + fitz Brian W. Fitzpatrick + daniel Daniel Stenberg + cmpilato C. Michael Pilato + philip Philip Martin + jerenkrantz Justin Erenkrantz + rooneg Garrett Rooney + blair Blair Zajac + striker Sander Striker + dlr Daniel Rall + mbk Mark Benedetto King + jaa Jani Averbach + julianfoad Julian Foad + jszakmeister John Szakmeister + ehu Erik Hülsmann + breser Ben Reser + maxb Max Bowsher + dberlin Daniel Berlin + danderson David Anderson + ivan Ivan Zhakov + djames David James + pburba Paul Burba + glasser David Glasser + lgo Lieven Govaerts + hwright Hyrum Wright + vgeorgescu Vlad Georgescu + kameshj Kamesh Jayachandran + markphip Mark Phippard + arfrever Arfrever Frehtes Taifersar Arahesis + stsp Stefan Sperling + kou Kouhei Sutou + danielsh Daniel Shahaf + peters Peter Samuelson + rhuijben Bert Huijben + stylesen Senthil Kumaran S + steveking Stefan Küng + neels Neels J. Hofmeyr + jwhitlock Jeremy Whitlock + sbutler Stephen Butler + dannas Daniel Näslund + stefan2 Stefan Fuhrmann + jcorvel Johan Corveleyn + trent Trent Nelson + +[[END ACTIVE FULL COMMITTERS. LEAVE THIS LINE HERE; SCRIPTS LOOK FOR IT.]] + +Full committers who have asked to be listed as dormant: + + bdenny Brian Estlin + epg Eric Gillespie + kraai Matt Kraai + bcollins Ben Collins + djh D.J. Heap + dwhedon David Kimdon + jpieper Josh Pieper + kevin Kevin Pilch-Bisson + lundblad Peter N. Lundblad + malcolm Malcolm Rowe + naked Nuutti Kotivuori + ringstrom Tobias Ringström + + +Partial committers who have asked to be listed as dormant: + + kon Kalle Olavi Niemitalo (psvn.el) + rassilon Bill Tutt (Win32, COM, issue-1003-dev br.) + pll Paul lussier (releases) + rdonch Роман Донченко (Swig-Python b.) + + +Commit access for specific areas: + + Bindings: + + pmayweg Patrick Mayweg (JavaHL bindings) + rey4 Russell Yanofsky (Swig bindings) + clkao Chia-liang Kao (Swig-Perl b.) + joeswatosh Joe Swatosh (Swig-Ruby b.) + jrvernooij Jelmer Vernooij (Python bindings) + sage Sage LaTorra (Ctypes-Python b.) + vmpn Vladimir Berezniker (JavaHL bindings) + + Packages: + + dws David Summers (RPMs) + ebswift Troy Simpson (windows-innosetup) + + Miscellaneous: + + kbohling Kirby C. Bohling (tools/dev) [EMAIL + IS BOUNCING] + nsd Nick Duffek (doc) + xsteve Stefan Reichör (psvn.el) + josander Jostein Andersen (various) + niemeyer Gustavo Niemeyer (svnperms.py) + [EMAIL IS BOUNCING] + zbrown Zack Brown (doc) [EMAIL IS + BOUNCING] + mprice Michael Price (releases) + jrepenning Jack Repenning (tools/dev) + jlonestar Martin Maurer (svnshow) [EMAIL + IS BOUNCING] + shlomif Shlomi Fish (svn-push) + mthelen Michael W Thelen (doc) + jeremybettis Jeremy Bettis (case-insensitive) + martinto Martin Tomes (case-insensitive) + danpat Daniel Patterson (svn-graph.pl) + archiecobbs Archie Cobbs (svnmerge) [EMAIL + IS BOUNCING] + giovannibajo Giovanni Bajo (svnmerge) + offby1 Eric Hanchrow (doc) + nomis80 Simon Perreault (svn-clean) + jlvarner Joshua Varner (doc) + nori Kobayashi Noritada (Ruby tools, + po: ja) [EMAIL IS + BOUNCING] + mf Martin Furter (svnmirror.sh + svn-backup-dumps.py) + adejong Arthur de Jong (svn2cl) + wsanchez Wilfredo Sánchez (various contrib) + mhagger Michael Haggerty (svntest) + madanus Madan U S (svnmerge) [EMAIL + IS BOUNCING] + wein Mathias Weinert (mailer) + bhuvan Bhuvaneswaran A (svn2feed.py, + build/hudson) + aogier Anthony Ogier (svn-merge-vendor.py) + dkagedal David Kågedal (dsvn.el) + mattiase Mattias Engdegård (dsvn.el) + dustin Dustin J. Mitchell (svnmerge) + rocketraman Raman Gupta (svnmerge) + rhansen Richard Hansen (svnstsw) + larrys Larry Shatzer, Jr. (svn-keyword-check.pl) + nmiyo MIYOKAWA, Nobuyoshi (www: ja) + rocksun Rock Sun (www: zh) + kmradke Kevin Radke (add-needs-lock.py) + esr Eric S. Raymond (svncutter) + gmcdonald Gavin McDonald (build/hudson, + tools/buildbot) + artagnon Ramkumar Ramachandra (svnrdump, svntest) + arwin Arwin Arni (svn-bisect) + joes Joe Schaefer (svnpubsub) + prabhugs Prabhu Gnana Sundar (verify-keep-going) + + + Translation of message files: + + niqueco Nicolás Lichtmaier (po: es) + luebbe Lübbe Onken (po: de) + jensseidel Jens Seidel (po: de) + astieger Andreas Stieger (po: de) + oyvindmo Øyvind Møll (po: nb) + sunny256 Øyvind A. Holm (po: nb) + jzgoda Jaroslaw Zgoda (po: pl) + karolszk Karol Szkudlarek (po: pl) + plasma Wei-Hon Chen (po: zh_TW) + jihuang June-Yen Huang (po: zh_TW) [EMAIL + IS BOUNCING] + marcosc Marcos Chaves (po: pt_BR) + pynoos Hojin Choi (po: ko) + blueboh Jeong Seolin (po: ko) + dongsheng Dongsheng Song (po: zh_CN) + hynnet YingNing Huang (po: zh_CN) [EMAIL + IS BOUNCING] + lark Wang Jian (po: zh_CN) [EMAIL + IS BOUNCING] +giorgio_valoti Giorgio Valoti (po: it) + nebiac Federico Nebiacolombo (po: it) [EMAIL + IS BOUNCING] + fabien Fabien Coelho (po: fr) + marcelg Marcel Gosselin (po: fr) + + Experimental branches: + + ashod Ashod Nakashian (compressed- + pristines br.) + gthompson Glenn A. Thompson (pluggable-db br.) + sigfred Sigfred Håversen (svnserve-ssl br.) + [EMAIL IS BOUNCING] + pmarek Ph. Marek (meta-data-v br.) + jpeacock John Peacock (perl-bindings- + improvements br.) + nikclayton Nik Clayton (perl-bindings- + improvements br.) + cacknin Charles Acknin (svnpatch-diff + br.) + holden Holden Karau (scheme-bindings br.) + moklo Morten Kloster (diff-improvements br.) + vmpn Vladimir Berezniker (javahl-ra br.) + + Subprojects that are complete, abandoned or have moved elsewhere: + + xela Alexander Müller (Java JNI b.) + yoshiki Yoshiki Hayashi (Non-SWIG Ruby b.) + mmacek Marko Maček (cvs2svn branch) + mass David Waite (certs branch) + sergeyli Sergey A. Lipnevich (neon-0.24 port) + ballbach Michael Ballbach (Old Mandrake RPM) + morten Morten Ludvigsen (Swig-Java b.) + jespersm Jesper Steen Møller (Swig-Java b.) + knacke Kai Nacke (Swig-Java b.) + fmatias Féliciano Matias (doc: fr) + dimentiy Dmitriy O. Popkov (doc: ru) + khmarbaise Karl Heinz Marbaise (doc: de) + gerhardoettl Gerhard Oettl (doc: de) + beerfrick Ariel Arjona (doc: es) + gradha Grzegorz A. Hankiewicz (doc: es) + ruben Rubén Gómez (doc: es) + dbrouard Diego Brouard (doc: es) + firemeteor Guo Rui (issue-2843-dev + br.) + + +## Local Variables: +## coding:utf-8 +## End: +## vim:fileencoding=utf8 diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000000..55ca3445313c --- /dev/null +++ b/INSTALL @@ -0,0 +1,1466 @@ + ====================================== + INSTALLING SUBVERSION + A Quick Guide + ====================================== + +$LastChangedDate: 2013-06-05 04:00:25 +0000 (Wed, 05 Jun 2013) $ + + +Contents: + + I. INTRODUCTION + A. Audience + B. Dependency Overview + C. Dependencies in Detail + D. Documentation + + II. INSTALLATION + A. Building from a Tarball or RPM + B. Building the Latest Source under Unix + C. Building under Unix in Different Directories + D. Installing from a Zip or Installer File under Windows + E. Building the Latest Source under Windows + + III. BUILDING A SUBVERSION SERVER + A. Setting Up Apache + B. Making and Installing the Subversion Server + C. Configuring Apache for Subversion + D. Running and Testing + E. Alternative: 'svnserve' and ra_svn + + IV. PLATFORM-SPECIFIC ISSUES + A. Windows XP + B. Mac OS X + + V. PROGRAMMING LANGUAGE BINDINGS (PYTHON, PERL, RUBY, JAVA) + + + +I. INTRODUCTION + ============ + + A. Audience + + This document is written for people who intend to build + Subversion from source code. Normally, the only people who do + this are Subversion developers and package maintainers. + + If neither of these labels fits you, we recommend you find an + appropriate binary package of Subversion and install that. + While the Subversion project doesn't officially release binary + packages, a number of volunteers have made such packages + available for different operating systems. Most Linux and BSD + distributions already have Subversion packages ready to go via + standard packaging channels, and other volunteers have built + 'installers' for both Windows and OS X. Visit this page for + package links: + + http://subversion.apache.org/packages.html + + For those of you who still wish to build from source, Subversion + follows the Unix convention of "./configure && make", but it has + a number of dependencies. + + + B. Dependency Overview + + You'll need the following build tools to compile Subversion: + + * autoconf 2.59 or later (Unix only) + * libtool 1.4 or later (Unix only) + * a reasonable C compiler (gcc, Visual Studio, etc.) + + + Subversion also depends on the following third-party libraries: + + * libapr and libapr-util (REQUIRED for client and server) + + The Apache Portable Runtime (APR) library provides an + abstraction of operating-system level services such as file + and network I/O, memory management, and so on. It also + provides convenience routines for things like hashtables, + checksums, and argument processing. While it was originally + developed for the Apache HTTP server, APR is a standalone + library used by Subversion and other products. It is a + critical dependency for all of Subversion; it's the layer + that allows Subversion clients and servers to run on + different operating systems. + + * SQLite (REQUIRED for client and server) + + Subversion uses SQLite to manage some internal databases. + + * libz (REQUIRED for client and server) + + Subversion uses zlib for compressing binary differences. + These diff streams are used everywhere -- over the network, + in the repository, and in the client's working copy. + + * libserf (OPTIONAL for client) + + The Serf library allows the Subversion client to send HTTP + requests. This is necessary if you want your client to access + a repository served by the Apache HTTP server. There is an + alternate 'svnserve' server as well, though, and clients + automatically know how to speak the svnserve protocol. + Thus it's not strictly necessary for your client to be able + to speak HTTP... though we still recommend that your client + be built to speak both HTTP and svnserve protocols. + + * OpenSSL (OPTIONAL for client and server) + + OpenSSL enables your client to access SSL-encrypted https:// + URLs (using libserf) in addition to unencrypted http:// URLs. + To use SSL with Subversion's WebDAV server, Apache needs to be + compiled with OpenSSL as well. + + * Berkeley DB (OPTIONAL for client and server) + + There are two different repository 'back-end' + implementations. One implementation stores data in a flat + filesystem (known as FSFS); the other implementation stores + data in a Berkeley DB database (known as BDB). When you + create a repository, you have the option of specifying a + storage back-end. The Berkeley DB back-end will only be + available if the BDB libraries are discovered at compile + time. + + * libsasl (OPTIONAL for client and server) + + If the Cyrus SASL library is detected at compile time, then + the svn client (and svnserve server) will be able to utilize + SASL to do various forms of authentication when speaking the + svnserve protocol. + + * Python, Perl, Java, Ruby (OPTIONAL) + + Subversion is mostly a collection of C libraries with + well-defined APIs, with a small collection of programs that + use the APIs. If you want to build Subversion API bindings + for other languages, you need to have those languages + available at build time. + + * KDELibs, GNOME Keyring (OPTIONAL for client) + + Subversion contains optional support for storing passwords in + KWallet (KDE 4) or GNOME Keyring. + + * libmagic + + If the libmagic library is detected at compile time, + it will be used to determine mime-types of binary files + which are added to version control. Note that mime-types + configured via auto-props or the mime-types-file option + take precedence. + + C. Dependencies in Detail + + Subversion depends on a number of third party tools and libraries. + Some of them are only required to run a Subversion server; others + are necessary just for a Subversion client. This section explains + what other tools and libraries will be required so that Subversion + can be built with the set of features you want. + + On Unix systems, the './configure' script will tell you if you are + missing the correct version of any of the required libraries or + tools, so if you are in a real hurry to get building, you can skip + straight to section II. If you want to gather the pieces you will + need before starting out, however, you should read the following. + + If you're just installing a Subversion client, the Subversion + team has created a script that downloads the minimal prerequisite + libraries (Apache Portable Runtime, Sqlite, and Zlib). The script, + 'get-deps.sh', is available in the same directory as this file. + When run, it will place 'apr', 'apr-util', 'serf', 'zlib', and + 'sqlite-amalgamation' directories directly into your unpacked Subversion + distribution. With the exception of sqlite-amalgamation, they will + still need to be configured, built and installed explicitly, and + Subversion's own configure script may need to be told where to find + them, if they were not installed in standard system locations. + + Note: there are optional dependencies (such as openssl, swig, and httpd) + which get-deps.sh does not download. + + Note: Because previous builds of Subversion may have installed older + versions of these libraries, you may want to run some of the cleanup + commands described in section II.B before installing the following. + + + 1. Apache Portable Runtime 0.9.7 or 1.X.X (REQUIRED) + + Whenever you want to build any part of Subversion, you need the + Apache Portable Runtime (APR) and the APR Utility (APR-util) + libraries. + + + **************************************************************** + ** IMPORTANT ISSUE ABOUT APR VERSIONS: READ THIS. ** + ** ** + **************************************************************** + | | + | APR 0.9.X and 1.X are binary-incompatible. | + | | + | This means: | + | | + | - if you are already using Subversion with APR 0.9.X, and | + | then upgrade your libapr to 1.X without rebuilding | + | Subversion, things will break and segfault. | + | | + | - if your Subversion server libraries are linked to one | + | version of APR, but your Apache server is linked to a | + | different version, things will break and segfault. | + | | + | Subversion distribution dependencies: | + | ------------------------------------- | + | | + | For a long time, Subversion's main distribution contained | + | APR and APR-UTIL (both 0.9.x), plus a few other things that | + | we couldn't count on the installation system having. But | + | nowadays, Subversion's requirements are no longer exotic, | + | and so our main distribution contains just the Subversion | + | source code itself -- people compiling Subversion are | + | expected to either have the APR libraries already installed | + | on their system, or to be capable of fetching them easily. | + | | + | Note that it's *perfectly* safe to use APR 1.X from the | + | beginning. In fact, we recommend it. If you're building | + | Subversion for the first time, there's no compatibility | + | issue to worry about, so grab the latest version of APR. | + | | + | If you already have a Subversion installation using APR | + | 0.9.x, it's still possible to move to APR 1.X safely. Just | + | be sure to recompile Subversion (and Apache httpd if | + | necessary) after upgrading APR! | + |______________________________________________________________| + + + If you do not have a pre-installed APR and APR-util, you will need + to get these yourself: + + http://apr.apache.org/download.cgi + + On Unix systems, if you already have the APR libraries compiled and do + not wish to regenerate them from source code, then Subversion needs to + be able to find them. + + There are a couple of options to "./configure" that tell it where + to look for the APR and APR-util libraries. By default it will try + to locate the libraries using apr-config and apu-config scripts. + These scripts provide all the relevant information for the APR and + APR-util installations. + + If you want to specify the location of the APR library, you can use + the "--with-apr=" option of "./configure". It should be able to find + the apr-config script in the standard location under that directory + (e.g. ${prefix}/bin). + + Similarly, you can specify the location of APR-util using the + "--with-apr-util=" option to "./configure". It will look for the + apu-config script relative to that directory. + + For example, if you want to use the APR libraries you built + with the Apache httpd server, you could run: + + $ ./configure --with-apr=/usr/local/apache2 \ + --with-apr-util=/usr/local/apache2 ... + + Be sure to use a native Windows SVN client (as opposed to + Cygwin's version) so that the .dsp files get carriage-returns at + the ends of their lines. Otherwise Visual Studio will complain + that it doesn't recognize the .dsp files. + + If you use APR libraries checked out from svn in an Unix + environment, you need to run the 'buildconf' script in each + library's directory, to regenerate the configure scripts and + other files required for compiling the libraries: + + $ cd apr; ./buildconf; ./configure ...; make; make install; cd .. + + $ cd apr-util; ./buildconf; ./configure ...; make; make install; cd .. + + Configure build and install both libraries before running Subversion's + configure script. + + + 2. Zlib (REQUIRED) + + Subversion's binary-differencing engine depends on zlib for + compression. Most Unix systems have libz pre-installed, but + if you need it, you can get it from + + http://www.zlib.net + + + 3. autoconf 2.59 or newer (Unix only) + + This is required only if you plan to build from the latest source + (see section II.B). Generally only developers would be doing this. + + + 4. libtool 1.4 or newer (Unix only) + + This is required only if you plan to build from the latest source + (see section II.B). + + Note: Some systems (Solaris, for example) require libtool 1.4.3 or + newer. The autogen.sh script knows about that. + + + 5. Serf library 1.2.1 or newer (OPTIONAL) + + If you want your client to be able to speak to an Apache + server (via a http:// or https:// URL), you must link against + serf. Though optional, we strongly recommend this. + + In order to use ra_serf, you must install serf, and run Subversion's + ./configure with the argument --with-serf. If serf is installed in a + non-standard place, you should use + + --with-serf=/path/to/serf/install + + instead. + + Serf can be obtained via your system's package distribution + system or directly from http://code.google.com/p/serf/. + + For more information on serf and Subversion's ra_serf, see the file + subversion/libsvn_ra_serf/README. + + 6. OpenSSL (OPTIONAL) + + ### needs some updates. I think serf automagically handles + ### finding OpenSSL, but we may need more docco here. and w.r.t + ### zlib. + + The Serf library has support for SSL encryption by relying on the + OpenSSL library. + + a. Using OpenSSL on the client through Serf + + On Unix systems, to build Serf with OpenSSL, you need OpenSSL + installed on your system, and you must add "--with-ssl" as a + "./configure" parameter. If your OpenSSL installation is hard + for Serf to find, you may need to use "--with-libs=/path/to/lib" + in addition. In particular, on Red Hat (but not Fedora Core) it + is necessary to specify "--with-libs=/usr/kerberos" for OpenSSL + to be found. You can also specify a path to the zlib library + using "--with-libs". + + Under Windows, you can specify the paths to these libraries by + passing the options --with-zlib and --with-openssl to gen-make.py. + + ### Is that right? In-tree build of Neon was disabled in r875974. + This may now apply to Serf, or else gen-make.py should be + updated to remove such options. + + c. Using OpenSSL on the Apache server + + You can also add support for these features to an Apache httpd + server to be used for Subversion using the same support libraries. + The Subversion build system will not provide them, however. You + add them by specifying parameters to the "./configure" script of + the Apache Server instead. + + For getting SSL on your server, you would add the "--enable-ssl" + or "--with-ssl=/path/to/lib" option to Apache's "./configure" + script. Apache enables zlib support by default, but you can + specify a nonstandard location for the library with the + "--with-z=/path/to/dir" option. Consult the Apache documentation + for more details, and for other modules you may wish to install + to enhance your Subversion server. + + If you don't already have it, you can get a copy of OpenSSL, + including instructions for building and packaging on both Unix + systems and Windows, at: + + http://www.openssl.org/ + + + 7. Berkeley DB 4.X (OPTIONAL) + + Berkeley DB is needed to build a Subversion server that supports + the BDB repository filesystem, or to access a BDB repository on + local disk. If you will only use the FSFS repository filesystem, + or if you are building a Subversion client that will only speak + to remote (networked) repositories, you don't need it. + + The current recommended version is 4.4.20 or newer, which brings + auto-recovery functionality to the Berkeley DB database + environment. + + If you must use an older version of Berkeley DB, we *strongly* + recommend using 4.3 or 4.2 over the 4.1 or 4.0 versions. Not + only are these significantly faster and more stable, but they + also enable Subversion repositories to automatically clean up + database journal files to save disk space. + + You'll need Berkeley DB installed on your system. You can + get it from: + + http://www.oracle.com/technology/software/products/berkeley-db/index.html + + If you have Berkeley DB installed in a place not searched by default + for includes and libraries, add something like this: + + --with-berkeley-db=db.h:/usr/local/include/db4.7:/usr/local/lib/db4.7:db-4.7 + + to your `configure' switches, and the build process will use the + Berkeley DB header and library in the named directories. You may + need to use a different path, of course. Note that in order for + the detection to succeed, the dynamic linker must be able to find + the libraries at configure time. + + If you are on the Windows platform and want to build Subversion, + a precompiled version of the Berkeley DB library is available for + download at the Subversion web site "Documents & files" area: + + http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=688 + + Look in the "Releases > Windows > Windows BDB" section. + + + 8. Cyrus SASL library (OPTIONAL) + + If the Simple Authentication and Security Layer (SASL) library + is detected on your system, then the Subversion client and + svnserve server can utilize its abilities for various forms of + authentication. To learn more about SASL or to get the source + code, visit: + + http://freshmeat.net/projects/cyrussasl/ + + + 9. Apache Web Server 2.X (OPTIONAL) + + (http://httpd.apache.org/download.cgi) + + The Apache httpd server is one of two methods to make your Subversion + repository available over a network - the other is a custom server + program called svnserve, which requires no extra software packages. + Building Subversion, the Apache server, and the modules that Apache + needs to communicate with Subversion are complicated enough that there + is a whole section at the end of this document that describes how it + is done: See section III for details. + + + 10. Python 2.5 or newer (http://www.python.org/) (OPTIONAL) + + If you want to run "make check" or build from the latest source + under Unix as described in section II.B and III.D, install + Python 2.5 or higher on your system. The majority of the test + suite is written in Python, as is part of Subversion's build + system. + + + 11. Perl 5.8 or newer (Windows only) (OPTIONAL) + + To build Subversion under any of the MS Windows platforms, you + will also need Perl 5.8 or newer to run apr-util's w32locatedb.pl + script. + + + 12. MASM 6 or newer (Windows only, OPTIONAL) + + The Windows build scripts for Subversion can use the Microsoft + Macro Assembler (MASM) to build an optimized version of the ZLib + library. Make sure that the version of MASM you use is compatible + with the C compiler. If you're using MSVC 6, and don't have MASM 6, + a free MASM-compatible assembler is available here: + + http://www.masm32.com/ + + You only need ML.EXE and ML.ERR from this distribution. + + The VS.NET installation already contains MASM (but note, that + version if MASM is not compatible with MSVC 6). + + + 13. SQLite (REQUIRED) + + Subversion 1.7 requires SQLite version 3.6.18 or above. You can meet + this dependency several ways: + * Use an SQLite amalgamation file. + * Specify an SQLite installation to use. + * Let Subversion find an installed SQLite. + + To use an SQLite-provided amalgamation, just drop sqlite3.c into + Subversion's sqlite-amalgamation/ directory, or point to it with the + --with-sqlite configure option. This file also ships with the Subversion + dependencies distribution, or you can download it from SQLite: + + http://www.sqlite.org/download.html + + + 14. pkg-config (Unix only, OPTIONAL) + + Subversion uses pkg-config to find appropriate options used + at build time. + + + 15. D-Bus (Unix only, OPTIONAL) + + D-Bus is a message bus system. D-Bus is required for support for KWallet + and GNOME Keyring. pkg-config is needed to find D-Bus headers and library. + + + 16. Qt 4 (Unix only, OPTIONAL) + + Qt is a cross-platform application framework. QtCore, QtDBus and QtGui + modules are required for support for KWallet. pkg-config is needed + to find Qt headers and libraries. + + + 17. KDELibs 4 (Unix only, OPTIONAL) + + Subversion contains optional support for storing passwords in KWallet. + KDELibs contains core KDE libraries. Subversion uses libkdecore and libkdeui + libraries when support for KWallet is enabled. kde4-config is used to get + some necessary options. pkg-config, D-Bus and Qt 4 are also required. + If you want to build support for KWallet, then pass the '--with-kwallet' + option to `configure`. If KDE is installed in a non-standard prefix, then + use: + + --with-kwallet=/path/to/KDE/prefix + + 18. GLib 2 (Unix only, OPTIONAL) + + GLib is a general-purpose utility library. GLib is required for support + for GNOME Keyring. pkg-config is needed to find GLib headers and library. + + + 19. GNOME Keyring (Unix only, OPTIONAL) + + Subversion contains optional support for storing passwords in GNOME Keyring. + pkg-config is needed to find GNOME Keyring headers and library. D-Bus and + GLib are also required. If you want to build support for GNOME Keyring, + then pass the '--with-gnome-keyring' option to `configure`. + + + 20. Ctypesgen (OPTIONAL) + + Ctypesgen is Python wrapper generator for ctypes. It is used to generate + a part of Subversion Ctypes Python bindings (CSVN). If you want to build + CSVN, then pass the '--with-ctypesgen' option to `configure`. If ctypesgen.py + is installed in a non-standard place, then use: + + --with-ctypesgen=/path/to/ctypesgen.py + + For more information on CSVN, see subversion/bindings/ctypes-python/README. + + 21. libmagic (OPTIONAL) + + Subversion's configure script attempts to find libmagic automatically. + If it is installed in a non-standard location, then use: + + --with-libmagic=/path/to/libmagic/prefix + + The files include/magic.h and lib/libmagic.so.1.0 (or similar) + are expected beneath this prefix directory. If they cannot be + found Subversion will be compiled without support for libmagic. + + If libmagic is installed but support for it should not be compiled + in, then use: + + --with-libmagic=no + + If configure should fail when libmagic is not present, but only + the default locations should be searched, then use: + + --with-libmagic + + D. Documentation + + The primary documentation for Subversion is the free book + "Version Control with Subversion", a.k.a. "The Subversion Book", + obtainable from http://svnbook.red-bean.com/. + + Various additional documentation exists in the doc/ subdirectory of + the Subversion source. See the file doc/README for more information. + + + +II. INSTALLATION + ============ + + A. Building from a Tarball or RPM + ------------------------------ + + 1. Building from a Tarball + + Download the most recent distribution tarball from: + + http://subversion.apache.org/download/ + + Unpack it, and use the standard GNU procedure to compile: + + $ ./configure + $ make + # make install + + You can also run the full test suite by running 'make check'. + + + 2. Building from an RPM + + If you are using Linux (or any OS that can use RPM) then another + possibility is to download the binary RPM from the + http://summersoft.fay.ar.us/pub/subversion directory. + + Currently only Linux on the i386 platform is supported + using this method. You might also require additional RPMS + (which can be found in the above mentioned directory) to use the + subversion RPM depending on what packages you already have installed: + + subversion*.i386.rpm + apache*.i386.rpm (Version 2.0.49 or greater) + db*.i386.rpm (Version 4.0.14 or greater; version 4.3.27 or + 4.2.52 is preferred however) + expat (Comes with RedHat) + + After downloading, install it (as root user): + + # rpm -ivh subversion*.386.rpm (add other packages as necessary) + + Note: For an easy way to generate a new version of the RPM + source and binary package from the latest source code you + just checked out, see the packages/rpm/README file for a + one-line build procedure. + + + B. Building the Latest Source under Unix + ------------------------------------- + + These instructions assume you have already installed Subversion + and checked out a working copy of Subversion's own code -- + either the latest /trunk code, or some branch or tag. You also + need to have already installed whatever prerequisites that + version of Subversion requires (if you haven't, the ./configure + step should complain). + + You can discard the directory created by the tarball; you're + about to build the latest, greatest Subversion client. This is + the procedure Subversion developers use. + + First off, if you have any Subversion libraries lying around + from previous 'make installs', clean them up first! + + # rm -f /usr/local/lib/libsvn* + # rm -f /usr/local/lib/libapr* + # rm -f /usr/local/lib/libexpat* + # rm -f /usr/local/lib/libserf* + + Start the process by running "autogen.sh": + + $ sh ./autogen.sh + + This script will make sure you have all the necessary components + available to build Subversion. If any are missing, you will be + told where to get them from. (See the 'Build Requirements' in + section I.) + + Note: if the command "autoconf" on your machine does not run + autoconf 2.59 or later, but you do have a new enough autoconf + available, then you can specify the correct one with the + AUTOCONF variable. (The AUTOHEADER variable is similar.) This + may be required on Debian GNU/Linux, where "autoconf" is + actually a Perl script that attempts to guess which version is + required -- because of the interaction between Subversion's and + APR's configuration systems, the Perl script may get it wrong. + So for example, you might need to do: + + $ AUTOCONF=autoconf2.59 sh ./autogen.sh + + Once you've prepared the working copy by running autogen.sh, + just follow the usual configuration and build procedure: + + $ ./configure + $ make + # make install + + (Optionally, you might want to pass --enable-maintainer-mode to + the ./configure script. This enables debugging symbols in your + binaries (among other things) and most Subversion developers use it.) + + Since the resulting binary depends on shared libraries, the + destination library directory must be identified in your + operating system's library search path. That is in either + /etc/ld.so.conf or $LD_LIBRARY_PATH for Linux systems and in + /etc/rc.conf for FreeBSD, followed by a run of the 'ldconfig' + program. Check your system documentation for details. By + identifying the destination directory, Subversion will be able + to dynamically load repository access plugins. If you try to do + a checkout and see an error like: + + subversion/libsvn_ra/ra_loader.c:209: (apr_err=170000) + svn: Unrecognized URL scheme 'https://svn.apache.org/repos/asf/subversion/trunk' + + It probably means that the dynamic loader/linker can't find all + of the libsvn_* libraries. + + + C. Building under Unix in Different Directories + -------------------------------------------- + + It is possible to configure and build Subversion on Unix in a + directory other than the working copy. For example + + $ svn co https://svn.apache.org/repos/asf/subversion/trunk svn + $ cd svn + $ # get SQLite amalgamation if required + $ chmod +x autogen.sh + $ ./autogen.sh + $ mkdir ../obj + $ cd ../obj + $ ../svn/configure [...with options as appropriate...] + $ make + + puts the Subversion working copy in the directory svn and builds + it in a separate, parallel directory obj. + + Why would you want to do this? Well there are a number of + reasons... + + * You may prefer to avoid "polluting" the working copy with + files generated during the build. + + * You may want to put the build directory and the working + copy on different physical disks to improve performance. + + * You may want to separate source and object code and only + backup the source. + + * You may want to remote mount the working copy on multiple + machines, and build for different machines from the same + working copy. + + * You may want to build multiple configurations from the + same working copy. + + The last reason above is possibly the most useful. For instance + you can have separate debug and optimized builds each using the + same working copy. Or you may want a client-only build and a + client-server build. Using multiple build directories you can + rebuild any or all configurations after an edit without the need + to either clean and reconfigure, or identify and copy changes + into another working copy. + + + D. Installing from a Zip or Installer File under Windows + -------------------------------------------------------- + + Of all the ways of getting a Subversion client, this is the + easiest. Download a Zip (*.zip) or self-extracting installer + (*-setup.exe) file from: + + http://subversion.apache.org/packages#windows + + For a Zip file, run your unzipping utility (WinZIP, ZipGenius, + UltimateZIP, FreeZIP, whatever) and extract the DLLs and EXEs to + a directory of your choice. Included in the download is the SVN + client, the SVNADMIN administration tool, and the SVNLOOK + reporting tool. + + Note that if you need support for non-English locales you'll have + to set the APR_ICONV_PATH environment variable to the path of the + iconv directory in the folder that contains the Subversion install. + + You may also want to add the bin directory in the Subversion folder + to your PATH environment variable so as to not have to use the full + path when running Subversion commands. + + To test the installation, open a DOS box (run either "cmd" or + "command" from the Start menu's "Run..." menu option), change to + the directory you installed the executables into, and run: + + C:\test>svn co https://svn.apache.org/repos/asf/subversion/trunk svn + + This will get the latest Subversion sources and put them into the + "svn" subdirectory. + + If using a self-extracting .exe file, just run it instead of + unzipping it, to install Subversion. + + E. Building the Latest Source under Windows + ---------------------------------------- + + E.1 Prerequisites + + * Visual Studio 6 and service pack. It can be built with later versions + of Visual Studio (Visual Studio.NET 2002, 2003, 2005, 2008 and Visual + C++ Express 2005, 2008) but these instructions assume VS6. + * A recent Windows SDK. (Not needed with Visual Studio 2005 and later) + If you are using Visual Studio 6, you need the latest SDK which + is compatible with VC6, which is the one from february 2003. + You can get it from MSDN: + http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdk-full.htm + * Python 2.5 or higher, downloaded from http://www.python.org/ which is + used to generate the project files. + * Perl 5.8 or higher from http://www.activestate.com/ + * Awk (from http://www.cs.princeton.edu/~bwk/btl.mirror/awk95.exe) is + needed to compile Apache or APR. Note that this is the actual awk + program, not an installer - just rename it to awk.exe and it is + ready to use. + * Apache apr, apr-util, and optionally apr-iconv libraries, version + 0.9.12 or later. Included in both the Subversion dependencies ZIP file + and the Apache 2 source zip. If you are building from a Subversion + checkout and have not downloaded Apache 2, then get these 3 libraries + from http://www.apache.org/dist/apr/. + * ZLib 1.2 or higher is required and is included in the Subversion + dependencies zip file or can be obtained from http://www.zlib.org + * Either a Subversion client binary from http://subversion.apache.org/ to + do the initial checkout of the Subversion source or the zip file + source distribution. See the section "Bootstrapping from a Zip or + Installer File under Windows" above for more. + * A means of unpacking the files, e.g., WinZIP or similar. + + Additional Options + + * [Optional] Apache 2 source, downloaded from + http://httpd.apache.org/download.cgi, these instructions assume + version 2.0.58. This is only needed for building the Subversion + server Apache modules. Note that although Subversion will compile + against Apache 2.2.3 and APR 1.2.7, there is a bug that causes + runtime failures with Subversion on Windows. The fix is included in + APR 1.2.8 and will be bundled in the next HTTP Server release + (likely to be 2.2.4). + * [Optional] Apache 2 msi install file, also from + http://httpd.apache.org/download.cgi (required for running the + tests). Only needed for testing the server dso modules and if + you are using Visual Studio 6. + Note that if you are not using Visual Studio 6 (and you want to + run and test the server modules) then you must rebuild Apache + from source -- do not use the stock MSI since mixing C runtime + libraries is not supported. + * [Optional] Berkeley DB for backend support of the server + components -- versions 4.3.27 and 4.4.20 are available from + http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=688 + as db-4.3.27-win32.zip and db-4.4.20-win32.zip. + For more information see Section I.5. + * [Optional] Openssl 0.9.7f or higher can be obtained from + http://www.openssl.org/source/openssl-0.9.7f.tar.gz + * [Optional] A modified version of GNU libintl, called + svn-win32-libintl.zip, can be used for displaying localized + messages. Available at: + http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=2627 + * [Optional] GNU gettext for generating message catalog (.mo) + files from message translations. You can get the latest + binaries from http://gnuwin32.sourceforge.net/. You'll need the + binaries (gettext-0.14.1-bin.zip) and dependencies + (gettext-0.14.1-dep.zip). + * [Optional] An assembler, e.g., MASM32 from http://www.masm32.com/ + or nasm which is available from + http://www.nasm.us/pub/nasm/releasebuilds/?C=M;O=D + + E.2 Notes + + The Serf library supports secure connections with OpenSSL and + on-the-wire compression with zlib. If you want to use the + secure connections feature, you should pass the option + "--with-openssl" to the gen-make.py script. See Section I.11 for + more details. + + E.3 Preparation + + This section describes how to unpack the files to make a build tree. + + * Make a directory SVN and cd into it. + * Either checkout Subversion: + + svn co https://svn.apache.org/repos/asf/subversion/trunk src-trunk + + or unpack the zip file distribution and rename the directory to + src-trunk. + + * Install Visual Studio Environment. You either have to tell the + installer to register environment variables or run VCVARS32.BAT + before building anything. If you are using a newer Visual Studio, + use the 'Visual Studio 200x Command Prompt' on the Start menu. + * Install and register a recent Windows Core SDK if you are using + Visual Studio 6. This is a quote from the Microsoft February 2003 + SDK documentation: + + "To register the SDK bin, include, and library directories with + Microsoft Visual Studio® version 6.0 and Visual Studio .NET, + click Start, point to All Programs, point to Microsoft Platform + SDK February 2003, point to Visual Studio Registration, and then + click Register PSDK Directories with Visual Studio. This + registration process places the SDK bin, include, and library + directories at the beginning of the search paths, which ensures + that the latest headers and libraries are used when building + applications in the IDE. Note that for Visual Studio 6.0 + integration to succeed, Visual Studio 6.0 must run at least once + before you select Register PSDK Directories with Visual + Studio. Also note that when this option is run, the IDEs should + not be running." + + * Install Python and add it to your path + * Install Perl (it should add itself to the path) + * Copy AWK (awk95.exe) to awk.exe (e.g. SVN\awk\awk.exe) and add + the directory containing it (e.g. SVN\awk) to the path. + * Install Apache 2 using the msi file if you are going to test the + server dso modules and are using Visual Studio 6. You must build + and install it from source if you are not using Visual Studio 6 and + want to build and/or test the server modules. + * If you checked out Subversion from the repository then install the serf + sources into SVN\src-trunk\serf. + * If you want BDB backend support, extract the Berkeley DB files + into SVN\src-trunk\db4-win32. It's a good idea to add + SVN\src-trunk\db4-win32\bin to your PATH, so that Subversion can find + the Berkeley DB DLLs. + + [NOTE: This binary package of Berkeley DB is provided for + convenience only. Please don't address questions about + Berkeley DB that aren't directly related to using Subversion + to the project mailing list.] + + If you build Berkeley DB from the source, you will have to copy + the file db-x.x.x\build_win32\db.h to + SVN\src-trunk\db4-win32\include, and all the import libraries to + SVN\src-trunk\db4-win32\lib. Again, the DLLs should be somewhere in + your path. + + * If you want to build the server modules, extract Apache source into + SVN\httpd-2.x.x. + * If you are building from a checkout of Subversion, and you are NOT + building Apache, then you will need the APR libraries. Depending + on how you got your version of APR, either: + - Extract the APR, APR-util and APR-iconv source distributions into + SVN\apr, SVN\apr-util, and SVN\apr-iconv respectively. + Or: + - Extract the apr, apr-util and apr-iconv directories from the + srclib folder in the Apache httpd source into SVN\apr, + SVN\apr-util, and SVN\apr-iconv respectively. + * Extract the ZLib sources into SVN\zlib if you are not using the zlib + included in the dependencies zip file. + * If you want secure connection (https) client support, extract openssl + into SVN\openssl-x.x.x + * If you want localized message support, extract svn-win32-libintl.zip + into SVN\svn-win32-libintl and extract gettext-x.x.x-bin.zip and + gettext-x.x.x-dep.zip into SVN\gettext-x.x.x-bin. + Add SVN\gettext-x.x.x-bin\bin to your path. + * [Optional] Extract MASM32 (only the ML.EXE and ML.ERR files) into + SVN\asm (or extract nasm into SVN\asm) and put it in your path. + + E.4 Building the Binaries + + To build the binaries either follow the instructions here or use + build\win32\vc6-build.bat.in after editing its default paths to match + yours and saving it as vc6-build.bat. The vc6-build.bat does a full build + using all options so it requires Apache 2 source and the other optional + components. + + Start in the SVN directory you created. + + Set up the environment (commands should be one line even if wrapped here). + + C:>set VER=trunk + C:>set DIR=trunk + C:>set DRIVE=C + C:>set PYTHONDIR=C:\Python22 + C:>set AWKDIR=C:\SVN\Awk + C:>set ASMDIR=C:\SVN\asm + C:>set SDKINC=C:\Program Files\Microsoft SDK\include + C:>set SDKLIB=C:\Program Files\Microsoft SDK\lib + C:>set GETTEXTBIN=C:\SVN\gettext-0.14.1-bin\bin + C:>PATH=%PATH%;%DRIVE%:\SVN\src-%DIR%\db4-win32;%ASMDIR%; + %PYTHONDIR%;%AWKDIR%;%GETTEXTBIN% + C:>set INCLUDE=%SDKINC%;%INCLUDE% + C:>set LIB=%SDKLIB%;%LIB% + + OpenSSL + + C:>cd openssl-0.9.7f + C:>perl Configure VC-WIN32 + [*] C:>call ms\do_masm + C:>nmake -f ms\ntdll.mak + C:>cd out32dll + C:>call ..\ms\test + C:>cd ..\.. + + *Note: Use "call ms\do_nasm" if you have nasm instead of MASM, or + "call ms\do_ms" if you don't have an assembler. + + Apache 2 + + This step is only required for building the server dso modules. + + The Subversion gen-make.py script must be run before building Apache or + Apache and Subversion will be running incompatible versions of apr. + + C:>cd src-%DIR% + C:>python gen-make.py -t dsp --with-httpd=..\httpd-2.0.58 + --with-berkeley-db=db4-win32 --with-openssl=..\openssl-0.9.7f + --with-zlib=..\zlib --with-libintl=..\svn-win32-libintl + C:>cd .. + C:>set APACHEDIR=C:\Program Files\Apache Group\Apache2 + C:>msdev httpd-2.0.58\apache.dsw /MAKE "BuildBin - Win32 Release" + + Subversion + + Things to note: + + * If you don't want to build mod_dav_svn, omit the --with-httpd + option. The zip file source distribution contains apr, apr-util and + apr-iconv in the default build location. If you have downloaded the + apr files yourself you will have to tell the generator where to find + the APR libraries; the options are --with-apr, --with-apr-util and + --with-apr-iconv. + * If you would like a debug build substitute Debug for Release in + the msdev commands. + * There have been rumors that Subversion on Win32 can be built + using the latest cygwin, you probably don't want the zip file source + distribution though. ymmv. + * The /USEENV switch to msdev makes it take notice of the INCLUDE and + LIB environment variables, it also makes it ignore its own lib and + include settings so you need to have the Windows SDK lib and include + directories in the LIB and INCLUDE environment variables. Do *not* + use this switch when starting up the msdev Visual environment. If you + wish to build in the Visual environment the SDK lib and include + directories must be in the Tools/Options/Directories settings (if you + followed the 'Register the SDK with Visual Studio 6' instructions + above this has been done for you). + * If you are using Visual Studio .NET change -t dsw into -t vcproj and + add the --vsnet-version=200x option on the gen-make.py command. + In this case you will also have to distribute the C runtime dll with + the binaries. Also, since Apache/APR do not provide .vcproj files, + you will need to convert the Apache/APR .dsp files to .vcproj files + with Visual Studio before building -- just open the Apache .dsw file + and answer 'Yes To All' when the conversion dialog pops up, or you + can open the individual .dsp files and convert them one at a time. + The Apache/APR projects required by Subversion are: + apr-util\libaprutil.dsp, apr\libapr.dsp, + apr-iconv\libapriconv.dsp, apr-util\xml\expat\lib\xml.dsp, + apr-util\uri\gen_uri_delims.dsp (for APR 0.9.x), + apr-iconv\ccs\libapriconv_ccs_modules.dsp, and + apr-iconv\ces\libapriconv_ces_modules.dsp. + * If the server dso modules are being built and tested Apache must not + be running or the copy of the dso modules will fail. + + C:>cd src-%DIR% + + If Apache 2 has been built and the server modules are required then + gen-make.py will already have been run. If the source is from the zip + file, Apache 2 has not been built so gen-make.py must be run: + + C:>python gen-make.py -t dsp --with-berkeley-db=db4-win32 + --with-openssl=..\openssl-0.9.7f --with-zlib=..\zlib + --with-libintl=..\svn-win32-libintl + + Then build subversion: + + C:>msdev subversion_msvc.dsw /USEENV /MAKE "__ALL_TESTS__ - Win32 Release" + C:>cd .. + + Or, with Visual C++.NET 2002, 2003, 2005: + + C:>devenv subversion_vcnet.sln /build "Release" /project "__ALL_TESTS__" + C:>cd .. + + Or, with Visual C++ Express 2005: + + C:>msbuild subversion_vcnet.sln /t:__ALL_TESTS__ /p:Configuration=Release + C:>cd .. + + The binaries have now been built. + + E.5 Packaging the binaries + + You now need to copy the binaries ready to make the release zip + file. You also need to do this to run the tests as the new binaries + need to be in your path. You can use the build/win32/make_dist.py + script in the Subversion source directory to do that. + + [TBD: Describe how to do this. Note dependencies on zip, jar, doxygen.] + + E.6 Testing the Binaries + [TBD: It's been a long, long while since it was necessary to move + binaries around for testing. win-tests.py does that automagically. + Fix this section accordingly, and probably reorder, putting + the packaging at the end.] + + The build process creates the binary test programs but it does not + copy the client tests into the release test area. + + C:>cd src-%DIR% + C:>mkdir Release\subversion\tests\cmdline + C:>xcopy /S /Y subversion\tests\cmdline Release\subversion\tests\cmdline + + If the server dso modules have been built then copy the dso files and + dlls into the Apache modules directory. + + C:>copy Release\subversion\mod_dav_svn\mod_dav_svn.so "%APACHEDIR%"\modules + C:>copy Release\subversion\mod_authz_svn\mod_authz_svn.so + "%APACHEDIR%"\modules + C:>copy svn-win32-%VER%\bin\intl.dll "%APACHEDIR%\bin" + C:>copy svn-win32-%VER%\bin\iconv.dll "%APACHEDIR%\bin" + C:>copy svn-win32-%VER%\bin\libdb42.dll "%APACHEDIR%\bin" + C:>cd .. + + Put the svn-win32-trunk\bin directory at the start of your path so + you run the newly built binaries and not another version you might + have installed. + + Then run the client tests: + + C:>PATH=%DRIVE%:\SVN\svn-win32-%VER%\bin;%PATH% + C:>cd src-%DIR% + C:>python win-tests.py -c -r -v + + If the server dso modules were built configure Apache to use the + mod_dav_svn and mod_authz_svn modules by making sure these lines appear + uncommented in httpd.conf: + + LoadModule dav_module modules/mod_dav.so + LoadModule dav_fs_module modules/mod_dav_fs.so + LoadModule dav_svn_module modules/mod_dav_svn.so + LoadModule authz_svn_module modules/mod_authz_svn.so + + And further down the file add location directives to point to the + test repositories. Change the paths to the SVN directory you created + (paths should be on one line even if wrapped here): + + + DAV svn + SVNParentPath C:/SVN/src-trunk/Release/subversion/tests/cmdline/ + svn-test-work/repositories + + + + DAV svn + SVNPath c:/SVN/src-trunk/Release/subversion/tests/cmdline/ + svn-test-work/local_tmp/repos + + + Then restart Apache and run the tests: + + C:>python win-tests.py -c -r -v -u http://localhost + C:>cd .. + +III. BUILDING A SUBVERSION SERVER + ============================ + + Subversion has two servers you can choose from: svnserve and + Apache. svnserve is a small, lightweight server program that is + automatically compiled when you build Subversion's source. Apache + is a more heavyweight HTTP server, but tends to have more features. + + This section primarily focuses on how to build Apache and the + accompanying mod_dav_svn server module for it. If you plan to use + svnserve instead, jump right to section E for a quick explanation. + + + A. Setting Up Apache + ----------------- + + (Following the BOOTSTRAPPING FROM RPM procedures above will install and + build the latest Subversion server for Linux RedHat 7.1, 7.2, and PPC + Linux systems *IF* the apache-devel-2.0.41 or greater package is already + installed when the SUBVERSION RPM is built.) + + + 1. Obtaining and Installing Apache 2 + + Subversion tries to compile against the latest released version + of Apache httpd 2.X. The easiest thing for you to do is download + a source tarball of the latest release and unpack that. + + + **************************************************************** + ** IMPORTANT ISSUE ABOUT APACHE VERSIONS: READ THIS. ** + ** ** + **************************************************************** + | | + | First, be sure to read the APR version warning box, back in | + | section I.C.1, which explains that APR 0.9.x and 1.X are | + | binary-incompatible. | + | | + | Apache HTTPD 2.0 uses APR 0.9.x. | + | Apache HTTPD 2.2 uses APR 1.2.x. | + | | + | We recommend using the latest Apache. However, whatever | + | version you choose, you *must* ensure that Subversion | + | and Apache are using the same version of APR. If you don't, | + | things will segfault and break. | + |______________________________________________________________| + + + If you have questions about the Apache httpd 2.0 build, please consult + the httpd install documentation: + + http://httpd.apache.org/docs-2.0/install.html + + At the top of the httpd tree: + + $ ./buildconf + $ ./configure --enable-dav --enable-so --enable-maintainer-mode + + The first arg says to build mod_dav. + + The second arg says to enable shared module support which is needed + for a typical compile of mod_dav_svn (see below). + + The third arg says to include debugging information. If you + built Subversion with --enable-maintainer-mode, then you should + do the same for Apache; there can be problems if one was + compiled with debugging and the other without. + + Note: if you have multiple db versions installed on your system, + Apache might link to a different one than Subversion, causing + failures when accessing the repository through Apache. To prevent + this from happening, you have to tell Apache which db version to + use and where to find db. Add --with-dbm=db4 and + --with-berkeley-db=/usr/local/BerkeleyDB.4.2 to the configure + line. Make sure this is the same db as the one Subversion uses. + This note assumes you have installed Berkeley DB 4.2.52 + at its default locations. For more info about the db requirement, + see section I.5. + + You may also want to include other modules in your build. Add + --enable-ssl to turn on SSL support, and --enable-deflate to turn on + compression support, for example. Consult the Apache documentation + for more details. + + All instructions below assume you configured Apache to install + in its default location, /usr/local/apache2/; substitute + appropriately if you chose some other location. + + Compile and install apache: + + $ make && make install + + + B. Making and Installing the Subversion Apache Server Module + --------------------------------------------------------- + + Go back into your subversion working copy and run ./autogen.sh if + you need to. Then, assuming Apache httpd 2.0 is installed in the + standard location, run: + + $ ./configure + + Note: do *not* configure subversion with "--disable-shared"! + mod_dav_svn *must* be built as a shared library, and it will + look for other libsvn_*.so libraries on your system. + + If you see a warning message that the build of mod_dav_svn is + being skipped, this may be because you have Apache httpd 2.X + installed in a non-standard location. You can use the + "--with-apxs=" option to locate the apxs script: + + $ ./configure --with-apxs=/usr/local/apache2/bin/apxs + + Note: it *is* possible to build mod_dav_svn as a static library + and link it directly into Apache. Possible, but painful. Stick + with the shared library for now; if you can't, then ask. + + $ rm /usr/local/lib/libsvn* + + If you have old subversion libraries sitting on your system, + libtool will link them instead of the `fresh' ones in your tree. + Remove them before building subversion. + + $ make clean && make && make install + + After the make install, the Subversion shared libraries are in + /usr/local/lib/. mod_dav_svn.so should be installed in + /usr/local/apache2/modules/. + + + Section II.E explains how to build the server on Windows. + + + C. Configuring Apache for Subversion + --------------------------------- + + The following section is an abbreviated version of the + information in the Subversion Book + (http://svnbook.red-bean.com). Please read chapter 6 for more + details. + + The following assumes you have already created a repository. + For documentation on how to do that, see README. + + The following also assumes that you have modified + /usr/local/apache2/conf/httpd.conf to reflect your setup. + At a minimum you should look at the User, Group and ServerName + directives. Full details on setting up apache can be found at: + http://httpd.apache.org/docs-2.0/ + + First, your httpd.conf needs to load the mod_dav_svn module. + Subversion's 'make install' target should automatically add this + line for you. But if apache gives you an error like "Unknown + DAV provider: svn", then you may want to verify that this line + exists in your httpd.conf: + + LoadModule dav_svn_module modules/mod_dav_svn.so + + NOTE: if you built mod_dav as a dynamic module as well, make sure + the above line appears after the one that loads mod_dav.so. + + Next, add this to the *bottom* of your httpd.conf: + + + DAV svn + SVNPath /absolute/path/to/repository + + + This will give anyone unrestricted access to the repository. If + you want limited access, read or write, you add these lines to + the Location block: + + AuthType Basic + AuthName "Subversion repository" + AuthUserFile /my/svn/user/passwd/file + + And: + + a) For a read/write restricted repository: + + Require valid-user + + b) For a write restricted repository: + + + Require valid-user + + + c) For separate restricted read and write access: + + AuthGroupFile /my/svn/group/file + + + Require group svn_committers + + + + Require group svn_committers + Require group svn_readers + + + These are only a few simple examples. For a complete tutorial + on Apache access control, please consider taking a look at the + tutorials found under "Security" on the following page: + http://httpd.apache.org/docs-2.0/misc/tutorials.html + + In order for 'svn cp' to work (which is actually implemented as a + DAV COPY command), mod_dav needs to be able to determine the + hostname of the server. A standard way of doing this is to use + Apache's ServerName directive to set the server's hostname. Edit + your /usr/local/apache2/conf/httpd.conf to include: + + ServerName svn.myserver.org + + If you are using virtual hosting through Apache's NameVirtualHost + directive, you may need to use the ServerAlias directive to specify + additional names that your server is known by. + + If you have configured mod_deflate to be in the server, you can enable + compression support for your repository by adding the following line + to your Location block: + + SetOutputFilter DEFLATE + + + NOTE: If you are unfamiliar with an Apache directive, or not exactly + sure about what it does, don't hesitate to look it up in the + documentation: http://httpd.apache.org/docs-2.0/mod/directives.html. + + NOTE: Make sure that the user 'nobody' (or whatever UID the + httpd process runs as) has permission to read and write the + Berkeley DB files! This is a very common problem. + + + D. Running and Testing + ------------------- + + Fire up apache 2: + + $ /usr/local/apache2/bin/apachectl stop + $ /usr/local/apache2/bin/apachectl start + + Check /usr/local/apache2/logs/error_log to make sure it started + up okay. + + Try doing a network checkout from the repository: + + $ svn co http://localhost/svn/repos wc + + The most common reason this might fail is permission problems + reading the repository db files. If the checkout fails, make + sure that the httpd process has permission to read and write to + the repository. You can see all of mod_dav_svn's complaints in + the Apache error logfile, /usr/local/apache2/logs/error_log. + + To run the regression test suite for networked Subversion, see + the instructions in subversion/tests/cmdline/README. + For advice about tracing problems, see "Debugging the server" in + http://subversion.apache.org/docs/community-guide/. + + + E. Alternative: 'svnserve' and ra_svn + ----------------------------------- + + An alternative network layer is libsvn_ra_svn (on the client + side) and the 'svnserve' process on the server. This is a + simple network layer that speaks a custom protocol over plain + TCP (documented in libsvn_ra_svn/protocol): + + $ svnserve -d # becomes a background daemon + $ svn checkout svn://localhost/usr/local/svn/repository + + You can use the "-r" option to svnserve to set a logical root + for repositories, and the "-R" option to restrict connections to + read-only access. ("Read-only" is a logical term here; svnserve + still needs write access to the database in this mode, but will + not allow commits or revprop changes.) + + 'svnserve' has built-in CRAM-MD5 authentication (so you can use + non-system accounts), and can also be tunneled over SSH (so you + can use existing system accounts). It's also capable of using + Cyrus SASL if libsasl2 is detected at ./configure time. Please + read chapter 6 in the Subversion Book + (http://svnbook.red-bean.com) for details on these features. + + + +IV. PLATFORM-SPECIFIC ISSUES + ======================== + + A. Windows XP + ---------- + + There is an error in the Windows XP TCP/IP stack which causes + corruption in certain cases. This problem is exposed only + through ra_dav. + + The root of the matter is caused by duplicating file handles + between parent and child processes. The httpd Apache group + explains this a lot better: + + http://www.apache.org/dist/httpd/binaries/win32/#xpbug + + And there's an item about this in the Subversion FAQ: + + http://subversion.apache.org/faq.html#windows-xp-server + + The only known workaround for now is to update to Windows XP + SP1 (or higher). + + + B. Mac OS X + -------- + + [TBD: Describe BDB 4.0.x problem] + + + +V. PROGRAMMING LANGUAGE BINDINGS (PYTHON, PERL, RUBY, JAVA) + ======================================================== + + For Python, Perl and Ruby bindings, see the file + + ./subversion/bindings/swig/INSTALL + + For Java bindings, see the file + + ./subversion/bindings/javahl/README diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..fd5f6ba3102e --- /dev/null +++ b/LICENSE @@ -0,0 +1,270 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +SUBVERSION SUBCOMPONENTS: + +Subversion includes a number of subcomponents with separate copyright +notices and license terms. Your use of the source code for the these +subcomponents is subject to the terms and conditions of the following +licenses. + +For portions of the Python bindings test suite at +subversion/bindings/swig/python/tests/trac/: + + I. Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + + II. Copyright (C) 2003, 2004, 2005 Edgewall Software + Copyright (C) 2003, 2004, 2005 Jonas Borgström + Copyright (C) 2005 Christopher Lenz + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + the documentation and/or other materials provided with the + distribution. + 3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +For the file subversion/libsvn_subr/utf_width.c + + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 000000000000..fdcd5445ede3 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,907 @@ +# +# Makefile.in: template Makefile for Subversion +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ==================================================================== +# + +top_builddir = . +top_srcdir = @top_srcdir@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +VPATH = @top_srcdir@ + +SVN_RA_LIB_DEPS = @SVN_RA_LIB_DEPS@ +SVN_RA_LIB_INSTALL_DEPS = @SVN_RA_LIB_INSTALL_DEPS@ +SVN_RA_LIB_LINK = @SVN_RA_LIB_LINK@ + +SVN_FS_LIB_DEPS = @SVN_FS_LIB_DEPS@ +SVN_FS_LIB_INSTALL_DEPS = @SVN_FS_LIB_INSTALL_DEPS@ +SVN_FS_LIB_LINK = @SVN_FS_LIB_LINK@ + +SWIG_SRC_DIR = $(abs_srcdir)/subversion/bindings/swig +SWIG_BUILD_DIR = $(abs_builddir)/subversion/bindings/swig + +SCHEMA_DIR = subversion/svn/schema + +SVN_APR_LIBS = @SVN_APR_LIBS@ +SVN_APRUTIL_LIBS = @SVN_APRUTIL_LIBS@ +SVN_APR_MEMCACHE_LIBS = @SVN_APR_MEMCACHE_LIBS@ +SVN_DB_LIBS = @SVN_DB_LIBS@ +SVN_GPG_AGENT_LIBS = @SVN_GPG_AGENT_LIBS@ +SVN_GNOME_KEYRING_LIBS = @SVN_GNOME_KEYRING_LIBS@ +SVN_KWALLET_LIBS = @SVN_KWALLET_LIBS@ +SVN_MAGIC_LIBS = @SVN_MAGIC_LIBS@ +SVN_SASL_LIBS = @SVN_SASL_LIBS@ +SVN_SERF_LIBS = @SVN_SERF_LIBS@ +SVN_SQLITE_LIBS = @SVN_SQLITE_LIBS@ +SVN_XML_LIBS = @SVN_XML_LIBS@ +SVN_ZLIB_LIBS = @SVN_ZLIB_LIBS@ + +LIBS = @LIBS@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +libdir = @libdir@ +fsmod_libdir = @libdir@ +ramod_libdir = @libdir@ +bdb_libdir = @libdir@ +gnome_keyring_libdir = @libdir@ +gpg_agent_libdir = @libdir@ +kwallet_libdir = @libdir@ +serf_libdir = @libdir@ +bindir = @bindir@ +includedir = @includedir@ +mandir = @mandir@ +srcdir = @srcdir@ +canonicalized_srcdir = @canonicalized_srcdir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +localedir = @localedir@ + +# where to install libsvn_swig_* +swig_py_libdir = @libdir@ +swig_pl_libdir = @libdir@ +swig_rb_libdir = @libdir@ + +### these possibly need further discussion +swig_pydir = @libdir@/svn-python/libsvn +swig_pydir_extra = @libdir@/svn-python/svn +swig_pldir = @libdir@/svn-perl +swig_rbdir = $(SWIG_RB_SITE_ARCH_DIR)/svn/ext +toolsdir = @bindir@/svn-tools + +javahl_javadir = @libdir@/svn-javahl +javahl_javahdir = @libdir@/svn-javahl/include +javahl_libdir = @libdir@ +javahl_test_rootdir=$(abs_builddir)/subversion/bindings/javahl/test-work +javahl_test_srcdir=$(abs_srcdir)/subversion/bindings/javahl + +gnome_auth_dir = $(abs_builddir)/subversion/libsvn_auth_gnome_keyring/.libs +kwallet_auth_dir = $(abs_builddir)/subversion/libsvn_auth_kwallet/.libs +auth_plugin_dirs = $(gnome_auth_dir):$(kwallet_auth_dir) + +MSGFMT = @MSGFMT@ +MSGFMTFLAGS = @MSGFMTFLAGS@ +MSGMERGE = @MSGMERGE@ +XGETTEXT = @XGETTEXT@ +TRANG = @TRANG@ + +PACKAGE_NAME=@PACKAGE_NAME@ +PACKAGE_VERSION=@PACKAGE_VERSION@ + +CC = @CC@ +CXX = @CXX@ +EXEEXT = @EXEEXT@ + +SHELL = @SHELL@ +LIBTOOL = @SVN_LIBTOOL@ +LTFLAGS = --tag=CC --silent +LTCXXFLAGS = --tag=CXX --silent +LT_CFLAGS = @LT_CFLAGS@ +LT_LDFLAGS = @LT_LDFLAGS@ +LT_SO_VERSION = @SVN_LT_SOVERSION@ +LT_NO_UNDEFINED = @LT_NO_UNDEFINED@ +LT_CXX_LIBADD = @LT_CXX_LIBADD@ + +INCLUDES = -I$(top_srcdir)/subversion/include -I$(top_builddir)/subversion \ + @SVN_APR_INCLUDES@ @SVN_APRUTIL_INCLUDES@ @SVN_APR_MEMCACHE_INCLUDES@ \ + @SVN_DB_INCLUDES@ @SVN_GNOME_KEYRING_INCLUDES@ \ + @SVN_KWALLET_INCLUDES@ @SVN_MAGIC_INCLUDES@ \ + @SVN_SASL_INCLUDES@ @SVN_SERF_INCLUDES@ @SVN_SQLITE_INCLUDES@ \ + @SVN_XML_INCLUDES@ @SVN_ZLIB_INCLUDES@ + +APACHE_INCLUDES = @APACHE_INCLUDES@ +APACHE_LIBEXECDIR = $(DESTDIR)@APACHE_LIBEXECDIR@ +APACHE_LDFLAGS = @APACHE_LDFLAGS@ + +SWIG = @SWIG@ +SWIG_PY_INCLUDES = @SWIG_PY_INCLUDES@ -I$(SWIG_SRC_DIR)/python/libsvn_swig_py +SWIG_PY_COMPILE = @SWIG_PY_COMPILE@ +SWIG_PY_LINK = @SWIG_PY_LINK@ +SWIG_PY_LIBS = @SWIG_PY_LIBS@ +SWIG_PL_INCLUDES = @SWIG_PL_INCLUDES@ +SWIG_RB_INCLUDES = @SWIG_RB_INCLUDES@ -I$(SWIG_SRC_DIR)/ruby/libsvn_swig_ruby +SWIG_RB_COMPILE = @SWIG_RB_COMPILE@ +SWIG_RB_LINK = @SWIG_RB_LINK@ +SWIG_RB_LIBS = @SWIG_RB_LIBS@ +SWIG_RB_SITE_LIB_DIR = @SWIG_RB_SITE_LIB_DIR@ +SWIG_RB_SITE_ARCH_DIR = @SWIG_RB_SITE_ARCH_DIR@ +SWIG_RB_TEST_VERBOSE = @SWIG_RB_TEST_VERBOSE@ +SWIG_RB_RI_DATADIR = $(DESTDIR)$(datadir)/ri/$(RUBY_MAJOR).$(RUBY_MINOR)/site + +CTYPESGEN = @CTYPESGEN@ +CTYPES_PYTHON_SRC_DIR = $(abs_srcdir)/subversion/bindings/ctypes-python + +JAVAHL_JAR=subversion/bindings/javahl/svn-javahl.jar +JAVAHL_INCLUDES= @JNI_INCLUDES@ -I$(abs_builddir)/subversion/bindings/javahl/include + +CXXHL_INCLUDES = -I$(abs_srcdir)/subversion/bindings/cxxhl/include + +SVN_APR_CONFIG = @SVN_APR_CONFIG@ +SVN_APR_INCLUDES = @SVN_APR_INCLUDES@ +SVN_APRUTIL_CONFIG = @SVN_APRUTIL_CONFIG@ +SVN_APRUTIL_INCLUDES = @SVN_APRUTIL_INCLUDES@ + +MKDIR = @MKDIR@ + +DOXYGEN = @DOXYGEN@ + +# The EXTRA_ parameters can be used to pass extra flags at 'make' time. +CFLAGS = @CFLAGS@ $(EXTRA_CFLAGS) +CMODEFLAGS = @CMODEFLAGS@ +CMAINTAINERFLAGS = @CMAINTAINERFLAGS@ +CXXFLAGS = @CXXFLAGS@ $(EXTRA_CXXFLAGS) +CXXMODEFLAGS = @CXXMODEFLAGS@ +CXXMAINTAINERFLAGS = @CXXMAINTAINERFLAGS@ +### A few of the CFLAGS (e.g. -Wmissing-prototypes, -Wstrict-prototypes, +### -Wmissing-declarations) are not valid for C++, and should be somehow +### suppressed (but they may come from httpd or APR). +CPPFLAGS = @CPPFLAGS@ $(EXTRA_CPPFLAGS) +LDFLAGS = @LDFLAGS@ $(EXTRA_LDFLAGS) +SWIG_LDFLAGS = @SWIG_LDFLAGS@ $(EXTRA_SWIG_LDFLAGS) + +COMPILE = $(CC) $(CMODEFLAGS) $(CPPFLAGS) $(CMAINTAINERFLAGS) $(CFLAGS) $(INCLUDES) +COMPILE_CXX = $(CXX) $(CXXMODEFLAGS) $(CPPFLAGS) $(CXXMAINTAINERFLAGS) $(CXXFLAGS) $(INCLUDES) +LT_COMPILE = $(LIBTOOL) $(LTFLAGS) --mode=compile $(COMPILE) $(LT_CFLAGS) +LT_COMPILE_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=compile $(COMPILE_CXX) $(LT_CFLAGS) + +# Execute a command that loads libraries from the build dir +LT_EXECUTE = $(LIBTOOL) $(LTFLAGS) --mode=execute `for f in $(abs_builddir)/subversion/*/*.la; do echo -dlopen $$f; done` + +# special compilation for files destined for mod_dav_svn +COMPILE_APACHE_MOD = $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) $(CMODEFLAGS) $(CPPFLAGS) $(CFLAGS) $(CMAINTAINERFLAGS) $(LT_CFLAGS) $(APACHE_INCLUDES) $(INCLUDES) -o $@ -c + +# special compilation for files destined for libsvn_swig_* (e.g. swigutil_*.c) +COMPILE_SWIG_PY = $(LIBTOOL) $(LTFLAGS) --mode=compile $(SWIG_PY_COMPILE) $(CPPFLAGS) $(LT_CFLAGS) -DSWIGPYTHON $(SWIG_PY_INCLUDES) $(INCLUDES) -o $@ -c +COMPILE_SWIG_PL = $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) $(LT_CFLAGS) $(SWIG_PL_INCLUDES) $(INCLUDES) -o $@ -c +COMPILE_SWIG_RB = $(LIBTOOL) $(LTFLAGS) --mode=compile $(SWIG_RB_COMPILE) $(CPPFLAGS) $(LT_CFLAGS) $(SWIG_RB_INCLUDES) $(INCLUDES) -o $@ -c + +# special compilation for files destined for javahl (i.e. C++) +COMPILE_JAVAHL_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=compile $(COMPILE_CXX) $(LT_CFLAGS) $(JAVAHL_INCLUDES) -o $@ -c +COMPILE_JAVAHL_JAVAC = $(JAVAC) $(JAVAC_FLAGS) +COMPILE_JAVAHL_JAVAH = $(JAVAH) + +# special compilation for files destined for cxxhl +COMPILE_CXXHL_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=compile $(COMPILE_CXX) $(LT_CFLAGS) $(CXXHL_INCLUDES) -o $@ -c + +LINK = $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LT_LDFLAGS) $(CFLAGS) $(LDFLAGS) -rpath $(libdir) +LINK_LIB = $(LINK) $(LT_SO_VERSION) +LINK_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=link $(CXX) $(LT_LDFLAGS) $(CXXFLAGS) $(LDFLAGS) -rpath $(libdir) +LINK_CXX_LIB = $(LINK_CXX) $(LT_SO_VERSION) + +# special link rule for mod_dav_svn +LINK_APACHE_MOD = $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LT_LDFLAGS) $(CFLAGS) $(LDFLAGS) -rpath $(APACHE_LIBEXECDIR) -avoid-version -module $(APACHE_LDFLAGS) + +# Special LDFLAGS for some libraries +libsvn_auth_gnome_keyring_LDFLAGS = @libsvn_auth_gnome_keyring_LDFLAGS@ +libsvn_auth_kwallet_LDFLAGS = @libsvn_auth_kwallet_LDFLAGS@ +libsvn_client_LDFLAGS = @libsvn_client_LDFLAGS@ +libsvn_delta_LDFLAGS = @libsvn_delta_LDFLAGS@ +libsvn_diff_LDFLAGS = @libsvn_diff_LDFLAGS@ +libsvn_fs_LDFLAGS = @libsvn_fs_LDFLAGS@ +libsvn_fs_base_LDFLAGS = @libsvn_fs_base_LDFLAGS@ +libsvn_fs_fs_LDFLAGS = @libsvn_fs_fs_LDFLAGS@ +libsvn_fs_util_LDFLAGS = @libsvn_fs_util_LDFLAGS@ +libsvn_ra_LDFLAGS = @libsvn_ra_LDFLAGS@ +libsvn_ra_local_LDFLAGS = @libsvn_ra_local_LDFLAGS@ +libsvn_ra_serf_LDFLAGS = @libsvn_ra_serf_LDFLAGS@ +libsvn_ra_svn_LDFLAGS = @libsvn_ra_svn_LDFLAGS@ +libsvn_repos_LDFLAGS = @libsvn_repos_LDFLAGS@ +libsvn_subr_LDFLAGS = @libsvn_subr_LDFLAGS@ +libsvn_wc_LDFLAGS = @libsvn_wc_LDFLAGS@ + +# Compilation of SWIG-generated C source code +COMPILE_PY_WRAPPER = $(LIBTOOL) $(LTFLAGS) --mode=compile $(SWIG_PY_COMPILE) $(LT_CFLAGS) $(CPPFLAGS) $(SWIG_PY_INCLUDES) -prefer-pic -c -o $@ +COMPILE_RB_WRAPPER = $(LIBTOOL) $(LTFLAGS) --mode=compile $(SWIG_RB_COMPILE) $(LT_CFLAGS) $(CPPFLAGS) $(SWIG_RB_INCLUDES) -prefer-pic -c -o $@ + +# these commands link the wrapper objects into an extension library/module +LINK_PY_WRAPPER = $(LIBTOOL) $(LTFLAGS) --mode=link $(SWIG_PY_LINK) $(SWIG_LDFLAGS) -rpath $(swig_pydir) -avoid-version -module +LINK_RB_WRAPPER = $(LIBTOOL) $(LTFLAGS) --mode=link $(SWIG_RB_LINK) $(SWIG_LDFLAGS) -rpath $(swig_rbdir) -avoid-version -module + +LINK_JAVAHL_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=link $(CXX) $(LT_LDFLAGS) $(CXXFLAGS) $(LDFLAGS) $(LT_CXX_LIBADD) -rpath $(libdir) + +INSTALL = @INSTALL@ +INSTALL_LIB = $(LIBTOOL) --mode=install $(INSTALL) +INSTALL_FSMOD_LIB = $(INSTALL_LIB) +INSTALL_RAMOD_LIB = $(INSTALL_LIB) +INSTALL_APR_MEMCACHE_LIB = $(INSTALL_LIB) +INSTALL_BDB_LIB = $(INSTALL_LIB) +INSTALL_GPG_AGENT_LIB = $(INSTALL_LIB) +INSTALL_GNOME_KEYRING_LIB = $(INSTALL_LIB) +INSTALL_KWALLET_LIB = $(INSTALL_LIB) +INSTALL_SERF_LIB = $(INSTALL_LIB) +INSTALL_BIN = $(LIBTOOL) --mode=install $(INSTALL) +INSTALL_CONTRIB = $(LIBTOOL) --mode=install $(INSTALL) +INSTALL_TOOLS = $(LIBTOOL) --mode=install $(INSTALL) +INSTALL_INCLUDE = $(INSTALL) -m 644 +INSTALL_MOD_SHARED = @APXS@ -i -S LIBEXECDIR="$(APACHE_LIBEXECDIR)" @MOD_ACTIVATION@ +INSTALL_DATA = $(INSTALL) -m 644 +INSTALL_LOCALE = $(INSTALL_DATA) +INSTALL_APACHE_MODS = @INSTALL_APACHE_MODS@ + +### this isn't correct yet +INSTALL_SWIG_PY = $(INSTALL_LIB) +INSTALL_SWIG_PY_LIB = $(INSTALL_LIB) +INSTALL_SWIG_PL_LIB = $(INSTALL_LIB) +INSTALL_SWIG_RB = $(INSTALL_LIB) +INSTALL_SWIG_RB_LIB = $(INSTALL_LIB) + +INSTALL_JAVAHL_LIB = $(INSTALL_LIB) + +# additional installation rules for the SWIG wrappers +INSTALL_EXTRA_SWIG_PY=\ + $(MKDIR) $(DESTDIR)$(swig_pydir); \ + $(MKDIR) $(DESTDIR)$(swig_pydir_extra); \ + for i in $(abs_srcdir)/subversion/bindings/swig/python/svn/*.py; do \ + $(INSTALL_DATA) "$$i" $(DESTDIR)$(swig_pydir_extra); \ + done; \ + for i in $(abs_srcdir)/subversion/bindings/swig/python/*.py; do \ + $(INSTALL_DATA) "$$i" $(DESTDIR)$(swig_pydir); \ + done; \ + if test "$(abs_srcdir)" != "$(abs_builddir)"; then \ + for i in $(abs_builddir)/subversion/bindings/swig/python/*.py; do \ + $(INSTALL_DATA) "$$i" $(DESTDIR)$(swig_pydir); \ + done; \ + fi; \ + $(PYTHON) -c 'import compileall; \ + compileall.compile_dir("$(DESTDIR)$(swig_pydir)", 1, "$(swig_pydir)"); \ + compileall.compile_dir("$(DESTDIR)$(swig_pydir_extra)", 1, \ + "$(swig_pydir_extra)");' + +# export an env variable so that the tests can run without being installed +TEST_SHLIB_VAR_SWIG_PY=\ + if [ "@SVN_APR_SHLIB_PATH_VAR@" = "DYLD_LIBRARY_PATH" ]; then \ + for d in $(SWIG_PY_DIR)/libsvn_swig_py $(SWIG_PY_DIR)/../../../libsvn_*; do \ + if [ -n "$$DYLD_LIBRARY_PATH" ]; then \ + @SVN_APR_SHLIB_PATH_VAR@="$$@SVN_APR_SHLIB_PATH_VAR@:$$d/.libs"; \ + else \ + @SVN_APR_SHLIB_PATH_VAR@="$$d/.libs"; \ + fi; \ + done; \ + export @SVN_APR_SHLIB_PATH_VAR@; \ + fi; + +# The path to generated and complementary source files for the SWIG +# bindings. +SWIG_PL_DIR = $(abs_builddir)/subversion/bindings/swig/perl +SWIG_PY_DIR = $(abs_builddir)/subversion/bindings/swig/python +SWIG_RB_DIR = $(abs_builddir)/subversion/bindings/swig/ruby + +# The path to the source files for the SWIG bindings +SWIG_PL_SRC_DIR = $(abs_srcdir)/subversion/bindings/swig/perl +SWIG_PY_SRC_DIR = $(abs_srcdir)/subversion/bindings/swig/python +SWIG_RB_SRC_DIR = $(abs_srcdir)/subversion/bindings/swig/ruby + +### Automate JAR creation using Makefile generator's javahl-java.jar +### property. Enhance generator to support JAR installation. +JAVAHL_MANIFEST_IN = $(abs_srcdir)/subversion/bindings/javahl/Manifest.in +JAVAHL_MANIFEST = subversion/bindings/javahl/Manifest +INSTALL_EXTRA_JAVAHL_JAVA=\ + sed s/%bundleVersion/$(PACKAGE_VERSION)/ $(JAVAHL_MANIFEST_IN) > $(JAVAHL_MANIFEST); \ + $(JAR) cfm $(JAVAHL_JAR) $(JAVAHL_MANIFEST) -C subversion/bindings/javahl/classes org; \ + $(INSTALL_DATA) $(JAVAHL_JAR) $(DESTDIR)$(javahl_javadir); + +INSTALL_EXTRA_JAVAHL_LIB=@INSTALL_EXTRA_JAVAHL_LIB@ + +INSTALL_EXTRA_SWIG_RB=\ + @echo $(MKDIR) $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \ + $(MKDIR) $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \ + for i in $(abs_srcdir)/subversion/bindings/swig/ruby/svn/*.rb; do \ + echo $(INSTALL_DATA) "$$i" $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \ + $(INSTALL_DATA) "$$i" $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \ + done + +# export an env variable so that the tests can run without being installed +TEST_SHLIB_VAR_SWIG_RB=\ + if [ "@SVN_APR_SHLIB_PATH_VAR@" = "DYLD_LIBRARY_PATH" ]; then \ + for d in $(SWIG_PY_DIR)/libsvn_swig_rb $(SWIG_PY_DIR)/../../../libsvn_*; do \ + if [ -n "$$DYLD_LIBRARY_PATH" ]; then \ + @SVN_APR_SHLIB_PATH_VAR@="$$@SVN_APR_SHLIB_PATH_VAR@:$$d/.libs"; \ + else \ + @SVN_APR_SHLIB_PATH_VAR@="$$d/.libs"; \ + fi; \ + done; \ + export @SVN_APR_SHLIB_PATH_VAR@; \ + fi; + +APXS = @APXS@ + +PYTHON = @PYTHON@ +PERL = @PERL@ + +JDK = @JDK@ +JAVA = @JAVA@ +JAVAC = @JAVAC@ +JAVADOC = @JAVADOC@ +JAVAC_FLAGS = @JAVAC_FLAGS@ +JAVAH = @JAVAH@ +JAR = @JAR@ + +JAVA_CLASSPATH=$(abs_srcdir)/subversion/bindings/javahl/src:@JAVA_CLASSPATH@ +javahl_java_CLASSPATH=$(JAVA_CLASSPATH) +javahl_compat_CLASSPATH=$(JAVA_CLASSPATH) +javahl_tests_CLASSPATH=$(JAVA_CLASSPATH) +javahl_compat_tests_CLASSPATH=$(JAVA_CLASSPATH) + +RUBY = @RUBY@ +RUBY_MAJOR = @RUBY_MAJOR@ +RUBY_MINOR = @RUBY_MINOR@ +RDOC = @RDOC@ + +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ + +TESTS = $(TEST_PROGRAMS) @BDB_TEST_PROGRAMS@ + +all: mkdir-init local-all +clean: local-clean +distclean: local-distclean +extraclean: local-extraclean +install: local-install revision-install + +@INCLUDE_OUTPUTS@ + +local-all: @BUILD_RULES@ @TRANSFORM_LIBTOOL_SCRIPTS@ + +transform-libtool-scripts: @BUILD_RULES@ + @$(SHELL) $(top_srcdir)/build/transform_libtool_scripts.sh + +locale-gnu-pot: + cd $(abs_srcdir) && XGETTEXT="$(XGETTEXT)" MSGMERGE="$(MSGMERGE)" \ + $(SHELL) tools/po/po-update.sh pot + +# "make locale-gnu-po-update" updates all translations. +# "make locale-gnu-po-update PO=ll" updates only the ll.po file. +locale-gnu-po-update: + cd $(abs_srcdir) && XGETTEXT="$(XGETTEXT)" MSGMERGE="$(MSGMERGE)" \ + $(SHELL) tools/po/po-update.sh $(PO) + +# clean everything but the bulky test output, returning the system back +# to before 'make' was run. +fast-clean: doc-clean + @list='$(BUILD_DIRS)'; for i in $$list; do \ + if [ -d $$i ]; then \ + (cd $$i && rm -f *.o *.lo *.la *.la-a *.spo *.mo && \ + rm -rf .libs); \ + fi \ + done + echo $(CLEAN_FILES) | xargs rm -f -- + find $(CTYPES_PYTHON_SRC_DIR) $(SWIG_PY_SRC_DIR) $(SWIG_PY_DIR) \ + $(abs_srcdir)/build $(top_srcdir)/subversion/tests/cmdline/svntest \ + -name "*.pyc" -exec rm {} ';' + +# clean everything, returning to before './configure' was run. +SVN_CONFIG_SCRIPT_FILES = @SVN_CONFIG_SCRIPT_FILES@ +local-distclean: local-clean + rm -fr config.cache config.log config.nice config.status \ + libtool mkmf.log subversion/svn_private_config.h \ + subversion/bindings/javahl/classes \ + subversion/bindings/javahl/include \ + $(SVN_CONFIG_SCRIPT_FILES) + rm -f Makefile + +# clean everything out, returning to before './autogen.sh' was run. +local-extraclean: extraclean-bindings local-distclean + rm -f $(top_srcdir)/build-outputs.mk \ + $(top_srcdir)/subversion/svn_private_config.h.in \ + $(top_srcdir)/configure \ + $(top_srcdir)/gen-make.opts \ + $(top_srcdir)/build/config.guess \ + $(top_srcdir)/build/config.sub \ + $(top_srcdir)/build/libtool.m4 \ + $(top_srcdir)/build/ltoptions.m4 \ + $(top_srcdir)/build/ltsugar.m4 \ + $(top_srcdir)/build/ltversion.m4 \ + $(top_srcdir)/build/lt~obsolete.m4 \ + $(top_srcdir)/build/ltmain.sh \ + $(top_srcdir)/build/transform_libtool_scripts.sh \ + $(EXTRACLEAN_FILES) + + +# clean everything, including test output. +local-clean: check-clean clean-bindings fast-clean + +local-install: @INSTALL_RULES@ + +revision-install: + test -d $(DESTDIR)$(includedir)/subversion-1 || \ + $(MKDIR) $(DESTDIR)$(includedir)/subversion-1 + (subversion/svnversion/svnversion $(top_srcdir) 2> /dev/null || \ + svnversion $(top_srcdir) 2> /dev/null || \ + echo "unknown"; \ + ) > $(DESTDIR)$(includedir)/subversion-1/svn-revision.txt + +install-static: @INSTALL_STATIC_RULES@ + +# JavaHL target aliases +javahl: mkdir-init javahl-java javahl-javah javahl-callback-javah javahl-types-javah javahl-lib @JAVAHL_TESTS_TARGET@ javahl-compat +install-javahl: javahl install-javahl-java install-javahl-javah install-javahl-lib +javahl-compat: javahl-compat-java @JAVAHL_COMPAT_TESTS_TARGET@ + +clean-javahl: + rm -rf $(javahl_java_PATH) $(javahl_javah_PATH) @JAVAHL_OBJDIR@ + rm -fr $(javahl_test_rootdir) + rm -f $(libsvnjavahl_PATH)/*.la $(JAVAHL_JAR) + rm -f $(libsvnjavahl_PATH)/*.lo + rm -f $(libsvnjavahl_PATH)/*.o + +check-tigris-javahl: javahl-compat + @FIX_JAVAHL_LIB@ + $(JAVA) "-Dtest.rootdir=$(javahl_test_rootdir)" "-Dtest.srcdir=$(javahl_test_srcdir)" "-Dtest.rooturl=$(BASE_URL)" "-Dtest.fstype=$(FS_TYPE)" -Djava.library.path=@JAVAHL_OBJDIR@:$(libdir) -classpath $(javahl_compat_tests_PATH):$(javahl_tests_CLASSPATH) "-Dtest.tests=$(JAVAHL_TESTS)" org.tigris.subversion.javahl.RunTests + +check-apache-javahl: javahl + @FIX_JAVAHL_LIB@ + $(JAVA) "-Dtest.rootdir=$(javahl_test_rootdir)" "-Dtest.srcdir=$(javahl_test_srcdir)" "-Dtest.rooturl=$(BASE_URL)" "-Dtest.fstype=$(FS_TYPE)" -Djava.library.path=@JAVAHL_OBJDIR@:$(libdir) -classpath $(javahl_tests_PATH):$(javahl_tests_CLASSPATH) "-Dtest.tests=$(JAVAHL_TESTS)" org.apache.subversion.javahl.RunTests + +check-javahl: check-apache-javahl + +check-all-javahl: check-apache-javahl check-tigris-javahl + +# "make check CLEANUP=true" will clean up directories for successful tests. +# "make check TESTS=subversion/tests/cmdline/basic_tests.py" +# will perform only basic tests (likewise for other tests). +check: bin @TRANSFORM_LIBTOOL_SCRIPTS@ $(TEST_DEPS) @BDB_TEST_DEPS@ + @if test "$(PYTHON)" != "none"; then \ + flags="--verbose"; \ + if test "$(CLEANUP)" != ""; then \ + flags="--cleanup $$flags"; \ + fi; \ + if test "$(BASE_URL)" != ""; then \ + flags="--url $(BASE_URL) $$flags"; \ + fi; \ + if test "$(FS_TYPE)" != ""; then \ + flags="--fs-type $(FS_TYPE) $$flags"; \ + fi; \ + if test "$(HTTP_LIBRARY)" != ""; then \ + flags="--http-library $(HTTP_LIBRARY) $$flags"; \ + fi; \ + if test "$(SERVER_MINOR_VERSION)" != ""; then \ + flags="--server-minor-version $(SERVER_MINOR_VERSION) $$flags"; \ + fi; \ + if test "$(ENABLE_SASL)" != ""; then \ + flags="--enable-sasl $$flags"; \ + fi; \ + if test "$(FSFS_SHARDING)" != ""; then \ + flags="--fsfs-sharding $(FSFS_SHARDING) $$flags"; \ + fi; \ + if test "$(FSFS_PACKING)" != ""; then \ + flags="--fsfs-packing $$flags"; \ + fi; \ + if test "$(PARALLEL)" != ""; then \ + flags="--parallel $$flags"; \ + fi; \ + if test "$(LOG_TO_STDOUT)" != ""; then \ + flags="--log-to-stdout $$flags"; \ + fi; \ + if test "$(MILESTONE_FILTER)" != ""; then \ + flags="--list --milestone-filter=$(MILESTONE_FILTER) \ + --mode-filter=$(MODE_FILTER) --log-to-stdout $$flags"; \ + fi; \ + if test "$(SET_LOG_LEVEL)" != ""; then \ + flags="--set-log-level $(SET_LOG_LEVEL) $$flags"; \ + fi; \ + if test "$(SSL_CERT)" != ""; then \ + flags="--ssl-cert $(SSL_CERT) $$flags"; \ + fi; \ + if test "$(HTTP_PROXY)" != ""; then \ + flags="--http-proxy $(HTTP_PROXY) $$flags"; \ + fi; \ + LD_LIBRARY_PATH='$(auth_plugin_dirs):$(LD_LIBRARY_PATH)' \ + $(PYTHON) $(top_srcdir)/build/run_tests.py \ + --config-file $(top_srcdir)/subversion/tests/tests.conf \ + $$flags \ + '$(abs_srcdir)' '$(abs_builddir)' $(TESTS); \ + else \ + echo "make check: Python 2.5 or greater is required,"; \ + echo " but was not detected during configure"; \ + exit 1; \ + fi; + +# First, set up Apache as documented in +# subversion/tests/cmdline/README. +davcheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ apache-mod + @$(MAKE) check BASE_URL=http://localhost + +# Automatically configure and run Apache httpd on a random port, and then +# run make check. +davautocheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ apache-mod + @# Takes MODULE_PATH, USE_HTTPV1 and SVN_PATH_AUTHZ in the environment. + @APXS=$(APXS) $(top_srcdir)/subversion/tests/cmdline/davautocheck.sh + +# First, run: +# subversion/svnserve/svnserve -d -r `pwd`/subversion/tests/cmdline +svncheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ + @$(MAKE) check BASE_URL=svn://127.0.0.1 + +# 'make svnserveautocheck' runs svnserve for you and kills it. +svnserveautocheck: svnserve bin $(TEST_DEPS) @BDB_TEST_DEPS@ + @env PYTHON=$(PYTHON) THREADED=$(THREADED) \ + $(top_srcdir)/subversion/tests/cmdline/svnserveautocheck.sh + +# First, run: +# subversion/svnserve/svnserve --listen-host "::1" -d -r `pwd`/subversion/tests/cmdline + +svncheck6: bin $(TEST_DEPS) @BDB_TEST_DEPS@ + @$(MAKE) check BASE_URL=svn://\[::1\] + +# First make sure you can ssh to localhost and that "svnserve" is in +# the path of the resulting shell. +svnsshcheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ + @$(MAKE) check \ + BASE_URL=svn+ssh://localhost`pwd`/subversion/tests/cmdline + +bdbcheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ + @$(MAKE) check FS_TYPE=bdb + +# Create an execution coverage report from the data collected during +# all execution since the last reset. +gcov: + lcov --capture -d . -b . -o gcov-lcov.dat > gcov-lcov.log + genhtml gcov-lcov.dat -o gcov-report > gcov-genhtml.log + +# Reset all execution coverage counters to zero. +gcov-reset: + lcov --zerocounters -d . + +# Remove the execution coverage data and the report. +gcov-clean: + rm -f gcov-lcov.dat gcov-lcov.log gcov-genhtml.log + rm -rf gcov-report + find . -name "*.gcda" -o -name "*.gcno" -print0 | xargs -0 rm -f -- + +check-clean: gcov-clean + if [ -d subversion/tests/cmdline/svn-test-work ]; then \ + find subversion/tests/cmdline/svn-test-work -mindepth 1 -maxdepth 1 \ + -print0 | xargs -0 rm -rf --; \ + fi + rm -rf subversion/tests/libsvn_fs/test-repo-* \ + subversion/tests/libsvn_fs_base/test-repo-* \ + subversion/tests/libsvn_fs_fs/test-repo-* \ + subversion/tests/libsvn_ra_local/test-repo-* \ + subversion/tests/libsvn_repos/test-repo-* \ + subversion/tests/libsvn_subr/z \ + subversion/tests/libsvn_wc/fake-wc \ + subversion/tests/libsvn_client/test-patch* \ + subversion/tests/libsvn_client/test-*/ \ + subversion/tests/libsvn_diff/B2 \ + subversion/tests/libsvn_diff/T1 \ + subversion/tests/libsvn_diff/T2 \ + subversion/tests/libsvn_diff/T3 \ + subversion/tests/svnserveautocheck.pid \ + tests.log fails.log + +mkdir-init: + @list='$(BUILD_DIRS) $(SCHEMA_DIR) doc'; \ + for i in $$list; do \ + if [ ! -d $$i ]; then \ + $(MKDIR) $$i ; \ + fi; \ + done + +# DOCUMENTATION RULES + +# Every single document in every format. +doc: doc-api doc-javahl + +# Generate API documentation for the C libraries. +### This could also generate POD for swig-perl, etc. +doc-api: mkdir-init + ( cd $(top_srcdir) && \ + sed "s,\(OUTPUT_DIRECTORY *= *\),\1$(abs_builddir)/," \ + doc/doxygen.conf | $(DOXYGEN) - ) + +# Generate API documentation for the JavaHL package. +doc-javahl: + $(JAVADOC) -d $(abs_builddir)/doc/javadoc \ + -sourcepath $(top_srcdir)/subversion/bindings/javahl/src \ + -link http://java.sun.com/javase/6/docs/api/ \ + org.tigris.subversion.javahl \ + org.apache.subversion.javahl \ + org.apache.subversion.javahl.callback \ + org.apache.subversion.javahl.types + +doc-clean: + rm -rf $(top_srcdir)/doc/doxygen + rm -rf $(top_srcdir)/doc/javadoc + +# Converting from the .rnc XML shcemas to various other schema formats. +SCHEMAS_DTD = $(SCHEMA_DIR)/blame.dtd $(SCHEMA_DIR)/info.dtd \ + $(SCHEMA_DIR)/list.dtd $(SCHEMA_DIR)/log.dtd \ + $(SCHEMA_DIR)/status.dtd $(SCHEMA_DIR)/props.dtd + +SCHEMAS_RNG = $(SCHEMA_DIR)/blame.rng $(SCHEMA_DIR)/info.rng \ + $(SCHEMA_DIR)/list.rng $(SCHEMA_DIR)/log.rng \ + $(SCHEMA_DIR)/status.rng $(SCHEMA_DIR)/props.rng + +SCHEMAS_XSD = $(SCHEMA_DIR)/blame.xsd $(SCHEMA_DIR)/info.xsd \ + $(SCHEMA_DIR)/list.xsd $(SCHEMA_DIR)/log.xsd \ + $(SCHEMA_DIR)/status.xsd $(SCHEMA_DIR)/props.xsd + +schema: schema-rng schema-dtd schema-xsd + +schema-rng: $(SCHEMAS_RNG) +schema-dtd: $(SCHEMAS_DTD) +schema-xsd: $(SCHEMAS_XSD) + +$(SCHEMAS_RNG) $(SCHEMAS_DTD) $(SCHEMAS_XSD): $(SCHEMA_DIR)/common.rnc + +schema-clean: + (cd $(SCHEMA_DIR) && rm -f *.rng *.dtd *.xsd) + +# +# Implicit rules for creating outputs from input files +# +.SUFFIXES: +.SUFFIXES: .c .cpp .lo .o .la-a .la \ + .po .spo .mo .rnc .rng .dtd .xsd .sql .h + +.sql.h: + $(PYTHON) $(top_srcdir)/build/transform_sql.py $< $(top_srcdir)/$@ + +.c.o: + $(COMPILE) -o $@ -c $< + +.cpp.o: + $(COMPILE_CXX) -o $@ -c $< + +.c.lo: + $(LT_COMPILE) -o $@ -c $< + +.cpp.lo: + $(LT_COMPILE_CXX) -o $@ -c $< + +.la.la-a: + sed "/library_names/s/'.*'/''/" $< > $@ + + +# Strip the Content-Type: header from the po file if we don't have a +# gettext that supports bind_textdomain_codeset, so it doesn't try +# to convert our UTF-8 .po files to the locale encoding. +@NO_GETTEXT_CODESET@.po.spo: +@NO_GETTEXT_CODESET@ sed \ +@NO_GETTEXT_CODESET@ '/^"Content-Type: text\/plain; charset=UTF-8\\n"$$/d' \ +@NO_GETTEXT_CODESET@ $< > $@ + +@NO_GETTEXT_CODESET@.spo.mo: +@NO_GETTEXT_CODESET@ $(MSGFMT) $(MSGFMTFLAGS) -o $@ $< + +# For systems with bind_textdomain_codeset, just leave the Content-Type: +# header alone. +@GETTEXT_CODESET@.po.mo: +@GETTEXT_CODESET@ $(MSGFMT) $(MSGFMTFLAGS) -o $@ $< + +.rnc.rng: + @TRANG@ $< $@ + +.rnc.dtd: + @TRANG@ $< $@ + +.rnc.xsd: + @TRANG@ $< $@ + +install-docs: install-man + +manroot = $(mandir)/man +install-man: + @list='$(MANPAGES)'; \ + for i in $$list; do \ + if test -f $(srcdir)/$$i; then file=$(srcdir)/$$i; \ + else file=$$i; fi; \ + ext=`echo $$i | sed -e 's/^.*\\.//'`; \ + $(MKDIR) $(DESTDIR)$(manroot)$$ext; \ + inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \ + inst=`echo $$inst | sed -e 's/^.*\///'`; \ + inst=`echo $$inst`.$$ext; \ + echo "$(INSTALL_DATA) $$file $(DESTDIR)$(manroot)$$ext/$$inst"; \ + $(INSTALL_DATA) $$file $(DESTDIR)$(manroot)$$ext/$$inst; \ + done + +install-swig-py: install-swig-py-lib +install-swig-rb: install-swig-rb-lib + +clean-bindings: clean-swig clean-ctypes-python clean-javahl + +extraclean-bindings: clean-swig extraclean-swig-headers \ + extraclean-swig-py extraclean-swig-rb \ + extraclean-swig-pl \ + clean-ctypes-python clean-javahl \ + +clean-swig: clean-swig-headers clean-swig-py clean-swig-rb clean-swig-pl + @rm -f .swig_checked + +EXTRACLEAN_SWIG_HEADERS=rm -f $(SWIG_SRC_DIR)/proxy/*.swg + +clean-swig-headers: + if test -z "$(RELEASE_MODE)"; then \ + $(EXTRACLEAN_SWIG_HEADERS); \ + fi + +extraclean-swig-headers: clean-swig-headers + $(EXTRACLEAN_SWIG_HEADERS) + +$(SWIG_PL_DIR)/native/Makefile.PL: $(SWIG_SRC_DIR)/perl/native/Makefile.PL.in + ./config.status subversion/bindings/swig/perl/native/Makefile.PL + +$(SWIG_PL_DIR)/native/Makefile: $(SWIG_PL_DIR)/native/Makefile.PL + cd $(SWIG_PL_DIR)/native; $(PERL) Makefile.PL + +# There is a "readlink -f" command on some systems for the same purpose, +# but it's not as portable (e.g. Mac OS X doesn't have it). These should +# only be used where Python/Perl are known to be available. +READLINK_PY=$(PYTHON) -c 'import sys,os; print(os.path.realpath(sys.argv[1]))' +READLINK_PL=$(PERL) -e 'use Cwd; print Cwd::realpath(shift)' + +swig-pl_DEPS = autogen-swig-pl libsvn_swig_perl \ + $(SWIG_PL_DIR)/native/Makefile +swig-pl: $(swig-pl_DEPS) + if test "`$(READLINK_PL) $(SWIG_PL_DIR)`" != "`$(READLINK_PL) $(SWIG_PL_SRC_DIR)`"; then \ + ln -sf $(SWIG_PL_SRC_DIR)/native/*.c $(SWIG_PL_DIR)/native; \ + fi + cd $(SWIG_PL_DIR)/native; $(MAKE) OPTIMIZE="" OTHERLDFLAGS="$(SWIG_LDFLAGS)" + +check-swig-pl: swig-pl swig-pl-lib + cd $(SWIG_PL_DIR)/native; $(MAKE) test + +install-swig-pl: swig-pl install-swig-pl-lib + cd $(SWIG_PL_DIR)/native; $(MAKE) install + +EXTRACLEAN_SWIG_PL=rm -f $(SWIG_PL_SRC_DIR)/native/svn_*.c \ + $(SWIG_PL_SRC_DIR)/native/core.c + +# Running Makefile.PL at this point *fails* (cannot find ..../.libs) so if the +# Makefile does not exist, DO NOT try to make it. But, if it doesn't exist, +# then the directory is probably clean anyway. +clean-swig-pl: + if test -z "$(RELEASE_MODE)"; then \ + $(EXTRACLEAN_SWIG_PL); \ + fi + for d in $(SWIG_PL_DIR)/libsvn_swig_perl; \ + do \ + cd $$d; \ + rm -rf *.lo *.la *.o .libs; \ + done + if [ -f "$(SWIG_PL_DIR)/native/Makefile" ]; then \ + cd $(SWIG_PL_DIR)/native; $(MAKE) clean; \ + fi + +extraclean-swig-pl: clean-swig-pl + $(EXTRACLEAN_SWIG_PL) + +$(SWIG_PY_DIR)/libsvn: + mkdir $(SWIG_PY_DIR)/libsvn + +copy-swig-py: autogen-swig-py $(SWIG_PY_DIR)/libsvn + @for f in $(SWIG_PY_SRC_DIR)/*.py $(SWIG_PY_DIR)/*.py; do \ + ! [ -f "$$f" ] || cp -pf $$f $(SWIG_PY_DIR)/libsvn; \ + done + @touch $(SWIG_PY_DIR)/libsvn/__init__.py + +swig-py: autogen-swig-py copy-swig-py + +check-swig-py: swig-py + $(TEST_SHLIB_VAR_SWIG_PY) \ + cd $(SWIG_PY_DIR); \ + $(PYTHON) $(SWIG_PY_SRC_DIR)/tests/run_all.py + +EXTRACLEAN_SWIG_PY=rm -rf $(SWIG_PY_SRC_DIR)/svn_*.c $(SWIG_PY_SRC_DIR)/core.c \ + $(SWIG_PY_SRC_DIR)/[a-z]*.py +clean-swig-py: + rm -rf $(SWIG_PY_DIR)/libsvn + if test -z "$(RELEASE_MODE)"; then \ + $(EXTRACLEAN_SWIG_PY); \ + fi + for d in $(SWIG_PY_DIR) $(SWIG_PY_DIR)/libsvn_swig_py; \ + do \ + cd $$d && rm -rf *.lo *.la *.o *.pyc .libs; \ + done + find $(SWIG_PY_SRC_DIR) $(SWIG_PY_DIR) -name "*.pyc" -exec rm {} ';' + +extraclean-swig-py: clean-swig-py + $(EXTRACLEAN_SWIG_PY) + +swig-rb: autogen-swig-rb + +check-swig-rb: swig-rb svnserve + $(TEST_SHLIB_VAR_SWIG_RB) \ + cd $(SWIG_RB_DIR); \ + if [ "$(RUBY_MAJOR)" -eq 1 -a "$(RUBY_MINOR)" -lt 9 ] ; then \ + $(RUBY) -I $(SWIG_RB_SRC_DIR) \ + $(SWIG_RB_SRC_DIR)/test/run-test.rb \ + --verbose=$(SWIG_RB_TEST_VERBOSE); \ + else \ + $(RUBY) -I $(SWIG_RB_SRC_DIR) \ + $(SWIG_RB_SRC_DIR)/test/run-test.rb; \ + fi + +EXTRACLEAN_SWIG_RB=rm -f $(SWIG_RB_SRC_DIR)/svn_*.c $(SWIG_RB_SRC_DIR)/core.c + +clean-swig-rb: + rm -rf $(SWIG_RB_DIR)/test/repos $(SWIG_RB_DIR)/test/wc + if test -z "$(RELEASE_MODE)"; then \ + $(EXTRACLEAN_SWIG_RB); \ + fi + for d in $(SWIG_RB_DIR) $(SWIG_RB_DIR)/libsvn_swig_ruby; \ + do \ + cd $$d; \ + rm -rf *.lo *.la *.o .libs; \ + done + +extraclean-swig-rb: clean-swig-rb + $(EXTRACLEAN_SWIG_RB) + +install-swig-rb-doc: + $(RDOC) --all --ri --op "$(SWIG_RB_RI_DATADIR)" "$(SWIG_RB_SRC_DIR)/svn" + +# ctypes-python make targets +ctypes-python: local-all + $(SHELL) $(abs_srcdir)/build/run_ctypesgen.sh "$(LT_EXECUTE)" "$(CPPFLAGS)" "$(EXTRA_CTYPES_LDFLAGS)" "$(PYTHON)" "$(CTYPESGEN)" "$(abs_srcdir)" "$(abs_builddir)" "$(libdir)" "$(SVN_APR_CONFIG)" "$(SVN_APRUTIL_CONFIG)" + +install-ctypes-python: ctypes-python + cd $(CTYPES_PYTHON_SRC_DIR); \ + $(PYTHON) setup.py install --prefix="$(DESTDIR)$(prefix)" + +check-ctypes-python: ctypes-python + cd $(CTYPES_PYTHON_SRC_DIR); \ + $(LT_EXECUTE) $(PYTHON) test/run_all.py + +# If any of those files exists, run ctypes' 'setup.py clean'. We don't run +# it otherwise because it spams stdout+stderr; see r1479326. +clean-ctypes-python: + cd $(CTYPES_PYTHON_SRC_DIR); \ + for b in build csvn/core/functions.py svn_all.py svn_all2.py ; do \ + if [ -e "$$b" ] ; then \ + $(PYTHON) setup.py clean --all; \ + break; \ + fi; \ + done + +# manually describe a dependency, which we won't otherwise detect +subversion/libsvn_wc/wc-queries.h: $(abs_srcdir)/subversion/libsvn_wc/wc-metadata.sql +subversion/libsvn_wc/wc-queries.h: $(abs_srcdir)/subversion/libsvn_wc/wc-checks.sql + +# Compatibility symlink. +# This runs after the target of the same name in build-outputs.mk. +INSTALL_EXTRA_TOOLS=\ + $(MKDIR) $(DESTDIR)$(bindir); \ + test -n "$$SVN_SVNMUCC_IS_SVNSYITF" && \ + ln -sf svnmucc$(EXEEXT) $(DESTDIR)$(bindir)/svnsyitf$(EXEEXT); \ + if test "$(DESTDIR)$(bindir)" != "$(DESTDIR)$(toolsdir)"; then \ + ln -sf $(DESTDIR)$(bindir)/svnmucc$(EXEEXT) $(DESTDIR)$(toolsdir)/svnmucc$(EXEEXT); \ + fi diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000000..4450bc3fc298 --- /dev/null +++ b/NOTICE @@ -0,0 +1,23 @@ +Subversion +Copyright 2010 The Apache Software Foundation + +This product includes software developed by many people, and distributed +under Contributor License Agreements to The Apache Software Foundation +(http://www.apache.org/). See the accompanying COMMITTERS file and the +revision logs for an exact contribution history. + +Portions of the test suite for Subversion's Python bindings are copyrighted +by Edgewall Software, Jonas Borgström and Christopher Lenz. +For more information, see LICENSE. + +This product includes software developed under the X Consortium License +see: build/install-sh + +This product includes software developed by Markus Kuhn under a permissive +license, see LICENSE. + +This software contains code derived from the RSA Data Security +Inc. MD5 Message-Digest Algorithm, including various +modifications by Spyglass Inc., Carnegie Mellon University, and +Bell Communications Research, Inc (Bellcore). + diff --git a/README b/README new file mode 100644 index 000000000000..ae91c90b7e46 --- /dev/null +++ b/README @@ -0,0 +1,84 @@ + + Subversion, a version control system. + ===================================== + +$LastChangedDate: 2012-02-10 14:58:53 +0000 (Fri, 10 Feb 2012) $ + +Contents: + + I. A FEW POINTERS + II. DOCUMENTATION + III. PARTICIPATING IN THE SUBVERSION COMMUNITY + IV. QUICKSTART GUIDE + V. CONVERTING FROM CVS + + + +I. A FEW POINTERS + + For an overview of the Subversion project, visit + + http://subversion.apache.org/ + + Once you have a Subversion client you can get the latest version + of the code with the command: + + $ svn co http://svn.apache.org/repos/asf/subversion/trunk subversion + + + +II. DOCUMENTATION + + The main documentation is the Subversion Book - an on-line version + can be found at: + + http://svnbook.red-bean.com/ + + It is written in DocBook XML, and the sources can be found at: + + http://svnbook.googlecode.com/svn/trunk/ + + If you wish to build the documentation from source, read the + src/en/README file within the book source. + + + +III. PARTICIPATING IN THE SUBVERSION COMMUNITY + + First, read http://subversion.apache.org/docs/community-guide/ + It describes Subversion coding and log message standards, as well + as how to join discussion lists. + + Talk on IRC with developers: irc.freenode.net, channel #svn-dev. + + Read the FAQ: http://subversion.apache.org/faq.html + + + +IV. QUICKSTART GUIDE + + See the final section of the first chapter of the Subversion Book. + + + +V. CONVERTING FROM CVS + + If you're a CVS user trying to move your CVS history over to + Subversion, then be sure to visit the 'cvs2svn' project: + + http://cvs2svn.tigris.org + + You can get the latest released version of the cvs2svn converter + from the project downloads area: + + http://cvs2svn.tigris.org/servlets/ProjectDocumentList?folderID=2976 + + Please note that the cvs2svn project is a *separate* project from + Subversion. If you have problems with cvs2svn or are confused, + please email the cvs2svn project's mailing lists, not the + Subversion lists. + + Finally, be sure to see Appendix B in the Subversion Book. It + contains a very quick overview of the major differences between + CVS and Subversion. + diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 000000000000..d92e4a23350b --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,55 @@ +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# aclocal.m4: Supplementary macros used by Subversion's configure.ac +# +# These are here rather than directly in configure.ac, since this prevents +# comments in the macro files being copied into configure.ac, producing +# useless bloat. (This is significant - a 12kB reduction in size!) + +# Include macros distributed by the APR project +sinclude(build/ac-macros/find_apr.m4) +sinclude(build/ac-macros/find_apu.m4) + +# Include Subversion's own custom macros +sinclude(build/ac-macros/svn-macros.m4) + +sinclude(build/ac-macros/apache.m4) +sinclude(build/ac-macros/apr.m4) +sinclude(build/ac-macros/aprutil.m4) +sinclude(build/ac-macros/apr_memcache.m4) +sinclude(build/ac-macros/berkeley-db.m4) +sinclude(build/ac-macros/compiler.m4) +sinclude(build/ac-macros/ctypesgen.m4) +sinclude(build/ac-macros/java.m4) +sinclude(build/ac-macros/sasl.m4) +sinclude(build/ac-macros/serf.m4) +sinclude(build/ac-macros/sqlite.m4) +sinclude(build/ac-macros/swig.m4) +sinclude(build/ac-macros/zlib.m4) +sinclude(build/ac-macros/kwallet.m4) +sinclude(build/ac-macros/macosx.m4) + +# Include the libtool macros +sinclude(build/libtool.m4) +sinclude(build/ltoptions.m4) +sinclude(build/ltsugar.m4) +sinclude(build/ltversion.m4) +sinclude(build/lt~obsolete.m4) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 000000000000..b8ba406eb818 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,210 @@ +#!/bin/sh +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# + +### Run this to produce everything needed for configuration. ### + + +# Run tests to ensure that our build requirements are met +RELEASE_MODE="" +RELEASE_ARGS="" +SKIP_DEPS="" +while test $# != 0; do + case "$1" in + --release) + RELEASE_MODE="$1" + RELEASE_ARGS="--release" + shift + ;; + -s) + SKIP_DEPS="yes" + shift + ;; + --) # end of option parsing + break + ;; + *) + echo "invalid parameter: '$1'" + exit 1 + ;; + esac +done +# ### The order of parameters is important; buildcheck.sh depends on it and +# ### we don't want to copy the fancy option parsing loop there. For the +# ### same reason, all parameters should be quoted, so that buildcheck.sh +# ### sees an empty arg rather than missing one. +./build/buildcheck.sh "$RELEASE_MODE" || exit 1 + +# Handle some libtool helper files +# +# ### eventually, we can/should toss this in favor of simply using +# ### APR's libtool. deferring to a second round of change... +# + +libtoolize="`./build/PrintPath glibtoolize libtoolize libtoolize15`" +lt_major_version=`$libtoolize --version 2>/dev/null | sed -e 's/^[^0-9]*//' -e 's/\..*//' -e '/^$/d' -e 1q` + +if [ "x$libtoolize" = "x" ]; then + echo "libtoolize not found in path" + exit 1 +fi + +rm -f build/config.guess build/config.sub +$libtoolize --copy --automake --force + +ltpath="`dirname $libtoolize`" +ltfile=${LIBTOOL_M4-`cd $ltpath/../share/aclocal ; pwd`/libtool.m4} + +if [ ! -f $ltfile ]; then + echo "$ltfile not found (try setting the LIBTOOL_M4 environment variable)" + exit 1 +fi + +echo "Copying libtool helper: $ltfile" +# An ancient helper might already be present from previous builds, +# and it might be write-protected (e.g. mode 444, seen on FreeBSD). +# This would cause cp to fail and print an error message, but leave +# behind a potentially outdated libtool helper. So, remove before +# copying: +rm -f build/libtool.m4 +cp $ltfile build/libtool.m4 + +for file in ltoptions.m4 ltsugar.m4 ltversion.m4 lt~obsolete.m4; do + rm -f build/$file + + if [ $lt_major_version -ge 2 ]; then + ltfile=${LIBTOOL_M4-`cd $ltpath/../share/aclocal ; pwd`/$file} + + if [ ! -f $ltfile ]; then + echo "$ltfile not found (try setting the LIBTOOL_M4 environment variable)" + exit 1 + fi + + echo "Copying libtool helper: $ltfile" + cp $ltfile build/$file + fi +done + +if [ $lt_major_version -ge 2 ]; then + for file in config.guess config.sub; do + configfile=${LIBTOOL_CONFIG-`cd $ltpath/../share/libtool/config ; pwd`/$file} + + if [ ! -f $configfile ]; then + echo "$configfile not found (try setting the LIBTOOL_CONFIG environment variable)" + exit 1 + fi + + cp $configfile build/$file + done +fi + +# Create the file detailing all of the build outputs for SVN. +# +# Note: this dependency on Python is fine: only SVN developers use autogen.sh +# and we can state that dev people need Python on their machine. Note +# that running gen-make.py requires Python 2.5 or newer. + +PYTHON="`./build/find_python.sh`" +if test -z "$PYTHON"; then + echo "Python 2.5 or later is required to run autogen.sh" + echo "If you have a suitable Python installed, but not on the" + echo "PATH, set the environment variable PYTHON to the full path" + echo "to the Python executable, and re-run autogen.sh" + exit 1 +fi + +# Compile SWIG headers into standalone C files if we are in release mode +if test -n "$RELEASE_MODE"; then + echo "Generating SWIG code..." + # Generate build-outputs.mk in non-release-mode, so that we can + # build the SWIG-related files + "$PYTHON" ./gen-make.py build.conf || gen_failed=1 + + # Build the SWIG-related files + make -f autogen-standalone.mk autogen-swig + + # Remove the .swig_checked file + rm -f .swig_checked +fi + +if test -n "$SKIP_DEPS"; then + echo "Creating build-outputs.mk (no dependencies)..." + "$PYTHON" ./gen-make.py $RELEASE_ARGS -s build.conf || gen_failed=1 +else + echo "Creating build-outputs.mk..." + "$PYTHON" ./gen-make.py $RELEASE_ARGS build.conf || gen_failed=1 +fi + +if test -n "$RELEASE_MODE"; then + find build/ -name '*.pyc' -exec rm {} \; +fi + +rm autogen-standalone.mk + +if test -n "$gen_failed"; then + echo "ERROR: gen-make.py failed" + exit 1 +fi + +# Produce config.h.in +echo "Creating svn_private_config.h.in..." +${AUTOHEADER:-autoheader} + +# If there's a config.cache file, we may need to delete it. +# If we have an existing configure script, save a copy for comparison. +if [ -f config.cache ] && [ -f configure ]; then + cp configure configure.$$.tmp +fi + +# Produce ./configure +echo "Creating configure..." +${AUTOCONF:-autoconf} + +# If we have a config.cache file, toss it if the configure script has +# changed, or if we just built it for the first time. +if [ -f config.cache ]; then + ( + [ -f configure.$$.tmp ] && cmp configure configure.$$.tmp > /dev/null 2>&1 + ) || ( + echo "Tossing config.cache, since configure has changed." + rm config.cache + ) + rm -f configure.$$.tmp +fi + +# Remove autoconf 2.5x's cache directory +rm -rf autom4te*.cache + +echo "" +echo "You can run ./configure now." +echo "" +echo "Running autogen.sh implies you are a maintainer. You may prefer" +echo "to run configure in one of the following ways:" +echo "" +echo "./configure --enable-maintainer-mode" +echo "./configure --disable-shared" +echo "./configure --enable-maintainer-mode --disable-shared" +echo "./configure --disable-optimize --enable-debug" +echo "./configure CUSERFLAGS='--flags-for-C' CXXUSERFLAGS='--flags-for-C++'" +echo "" +echo "Note: If you wish to run a Subversion HTTP server, you will need" +echo "Apache 2.x. See the INSTALL file for details." +echo "" diff --git a/build-outputs.mk b/build-outputs.mk new file mode 100644 index 000000000000..7bee70d5f7c1 --- /dev/null +++ b/build-outputs.mk @@ -0,0 +1,2894 @@ +# DO NOT EDIT -- AUTOMATICALLY GENERATED BY build/generator/gen_make.py +# FROM build/generator/templates/build-outputs.mk.ezt + +######################################## +# Section 1: Global make variables +######################################## + +FS_BASE_DEPS = subversion/libsvn_fs_base/libsvn_fs_base-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la +FS_BASE_LINK = ../../subversion/libsvn_fs_base/libsvn_fs_base-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la + +FS_FS_DEPS = subversion/libsvn_fs_fs/libsvn_fs_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la +FS_FS_LINK = ../../subversion/libsvn_fs_fs/libsvn_fs_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la + +RA_LOCAL_DEPS = subversion/libsvn_ra_local/libsvn_ra_local-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +RA_LOCAL_LINK = ../../subversion/libsvn_ra_local/libsvn_ra_local-1.la ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la + +RA_SERF_DEPS = subversion/libsvn_ra_serf/libsvn_ra_serf-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +RA_SERF_LINK = ../../subversion/libsvn_ra_serf/libsvn_ra_serf-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la + +RA_SVN_DEPS = subversion/libsvn_ra_svn/libsvn_ra_svn-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +RA_SVN_LINK = ../../subversion/libsvn_ra_svn/libsvn_ra_svn-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la + +BUILD_DIRS = subversion/tests/cmdline subversion/tests/libsvn_subr subversion/tests/libsvn_fs_base subversion/tests/libsvn_client subversion/tests/libsvn_wc subversion/bindings/cxxhl subversion/bindings/cxxhl/tests tools/diff subversion/tests/libsvn_diff subversion/tests/libsvn_fs_fs subversion/tests/libsvn_fs tools/dev tools/server-side subversion/bindings/javahl/src/org/apache/subversion/javahl/callback subversion/bindings/javahl/classes subversion/bindings/javahl/include subversion/bindings/javahl/src/org/tigris/subversion/javahl subversion/bindings/javahl/tests/org/tigris/subversion/javahl subversion/bindings/javahl/src/org/apache/subversion/javahl subversion/bindings/javahl/src/org/apache/subversion/javahl/types subversion/bindings/javahl/tests/org/apache/subversion/javahl subversion/libsvn_auth_gnome_keyring subversion/libsvn_auth_kwallet subversion/libsvn_client subversion/libsvn_delta subversion/libsvn_diff subversion/libsvn_fs subversion/libsvn_fs_base subversion/libsvn_fs_base/bdb subversion/libsvn_fs_base/util subversion/libsvn_fs_fs subversion/libsvn_fs_util subversion/libsvn_ra subversion/libsvn_ra_local subversion/libsvn_ra_serf subversion/libsvn_ra_svn subversion/libsvn_repos subversion/libsvn_subr subversion/bindings/swig/perl/libsvn_swig_perl subversion/bindings/swig/python/libsvn_swig_py subversion/bindings/swig/ruby/libsvn_swig_ruby subversion/tests subversion/libsvn_wc subversion/bindings/cxxhl/src subversion/bindings/javahl/native subversion/po subversion/mod_authz_svn subversion/mod_dav_svn subversion/mod_dav_svn/reports subversion/mod_dav_svn/posts tools/server-side/mod_dontdothat subversion/tests/libsvn_ra_local subversion/tests/libsvn_ra subversion/tests/libsvn_delta subversion/tests/libsvn_repos subversion/svn tools/client-side/svn-bench subversion/svnadmin subversion/svndumpfilter subversion/svnlook subversion/svnmucc tools/dev/svnraisetreeconflict subversion/svnrdump subversion/svnserve subversion/svnsync subversion/svnversion subversion/bindings/swig subversion/bindings/swig/python subversion/bindings/swig/perl subversion/bindings/swig/ruby subversion/bindings/swig/proxy + +BDB_TEST_DEPS = subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT) + +BDB_TEST_PROGRAMS = subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT) + +TEST_DEPS = subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) subversion/tests/libsvn_subr/auth-test$(EXEEXT) subversion/tests/libsvn_subr/cache-test$(EXEEXT) subversion/tests/libsvn_subr/checksum-test$(EXEEXT) subversion/tests/libsvn_client/client-test$(EXEEXT) subversion/tests/libsvn_subr/compat-test$(EXEEXT) subversion/tests/libsvn_subr/config-test$(EXEEXT) subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) subversion/tests/libsvn_subr/crypto-test$(EXEEXT) subversion/tests/libsvn_wc/db-test$(EXEEXT) subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) subversion/tests/cmdline/entries-dump$(EXEEXT) subversion/tests/libsvn_subr/error-code-test$(EXEEXT) subversion/tests/libsvn_subr/error-test$(EXEEXT) subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) subversion/tests/libsvn_fs/fs-test$(EXEEXT) subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) subversion/tests/libsvn_subr/io-test$(EXEEXT) subversion/tests/libsvn_fs/locks-test$(EXEEXT) subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) subversion/tests/libsvn_subr/opt-test$(EXEEXT) subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) subversion/tests/libsvn_subr/path-test$(EXEEXT) subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) subversion/tests/libsvn_ra/ra-test$(EXEEXT) subversion/tests/libsvn_delta/random-test$(EXEEXT) subversion/tests/libsvn_repos/repos-test$(EXEEXT) subversion/tests/libsvn_subr/revision-test$(EXEEXT) subversion/tests/libsvn_subr/skel-test$(EXEEXT) subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) subversion/tests/libsvn_subr/stream-test$(EXEEXT) subversion/tests/libsvn_subr/string-test$(EXEEXT) subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) subversion/tests/libsvn_subr/time-test$(EXEEXT) subversion/tests/libsvn_subr/translate-test$(EXEEXT) subversion/tests/libsvn_subr/utf-test$(EXEEXT) subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) subversion/tests/libsvn_wc/wc-test$(EXEEXT) subversion/tests/libsvn_delta/window-test$(EXEEXT) subversion/tests/cmdline/authz_tests.py subversion/tests/cmdline/autoprop_tests.py subversion/tests/cmdline/basic_tests.py subversion/tests/cmdline/blame_tests.py subversion/tests/cmdline/cat_tests.py subversion/tests/cmdline/changelist_tests.py subversion/tests/cmdline/checkout_tests.py subversion/tests/cmdline/commit_tests.py subversion/tests/cmdline/copy_tests.py subversion/tests/cmdline/depth_tests.py subversion/tests/cmdline/diff_tests.py subversion/tests/cmdline/entries_tests.py subversion/tests/cmdline/export_tests.py subversion/tests/cmdline/externals_tests.py subversion/tests/cmdline/getopt_tests.py subversion/tests/cmdline/history_tests.py subversion/tests/cmdline/import_tests.py subversion/tests/cmdline/info_tests.py subversion/tests/cmdline/input_validation_tests.py subversion/tests/cmdline/iprop_authz_tests.py subversion/tests/cmdline/iprop_tests.py subversion/tests/cmdline/lock_tests.py subversion/tests/cmdline/log_tests.py subversion/tests/cmdline/merge_authz_tests.py subversion/tests/cmdline/merge_automatic_tests.py subversion/tests/cmdline/merge_reintegrate_tests.py subversion/tests/cmdline/merge_tests.py subversion/tests/cmdline/merge_tree_conflict_tests.py subversion/tests/cmdline/mergeinfo_tests.py subversion/tests/cmdline/move_tests.py subversion/tests/cmdline/patch_tests.py subversion/tests/cmdline/prop_tests.py subversion/tests/cmdline/redirect_tests.py subversion/tests/cmdline/relocate_tests.py subversion/tests/cmdline/resolve_tests.py subversion/tests/cmdline/revert_tests.py subversion/tests/cmdline/schedule_tests.py subversion/tests/cmdline/special_tests.py subversion/tests/cmdline/stat_tests.py subversion/tests/cmdline/svnadmin_tests.py subversion/tests/cmdline/svnauthz_tests.py subversion/tests/cmdline/svndumpfilter_tests.py subversion/tests/cmdline/svnlook_tests.py subversion/tests/cmdline/svnmucc_tests.py subversion/tests/cmdline/svnrdump_tests.py subversion/tests/cmdline/svnsync_authz_tests.py subversion/tests/cmdline/svnsync_tests.py subversion/tests/cmdline/svnversion_tests.py subversion/tests/cmdline/switch_tests.py subversion/tests/cmdline/trans_tests.py subversion/tests/cmdline/tree_conflict_tests.py subversion/tests/cmdline/update_tests.py subversion/tests/cmdline/upgrade_tests.py subversion/tests/cmdline/wc_tests.py + +TEST_PROGRAMS = subversion/tests/libsvn_subr/auth-test$(EXEEXT) subversion/tests/libsvn_subr/cache-test$(EXEEXT) subversion/tests/libsvn_subr/checksum-test$(EXEEXT) subversion/tests/libsvn_client/client-test$(EXEEXT) subversion/tests/libsvn_subr/compat-test$(EXEEXT) subversion/tests/libsvn_subr/config-test$(EXEEXT) subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) subversion/tests/libsvn_subr/crypto-test$(EXEEXT) subversion/tests/libsvn_wc/db-test$(EXEEXT) subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) subversion/tests/libsvn_subr/error-code-test$(EXEEXT) subversion/tests/libsvn_subr/error-test$(EXEEXT) subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) subversion/tests/libsvn_fs/fs-test$(EXEEXT) subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) subversion/tests/libsvn_subr/io-test$(EXEEXT) subversion/tests/libsvn_fs/locks-test$(EXEEXT) subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) subversion/tests/libsvn_subr/opt-test$(EXEEXT) subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) subversion/tests/libsvn_subr/path-test$(EXEEXT) subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) subversion/tests/libsvn_ra/ra-test$(EXEEXT) subversion/tests/libsvn_delta/random-test$(EXEEXT) subversion/tests/libsvn_repos/repos-test$(EXEEXT) subversion/tests/libsvn_subr/revision-test$(EXEEXT) subversion/tests/libsvn_subr/skel-test$(EXEEXT) subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) subversion/tests/libsvn_subr/stream-test$(EXEEXT) subversion/tests/libsvn_subr/string-test$(EXEEXT) subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) subversion/tests/libsvn_subr/time-test$(EXEEXT) subversion/tests/libsvn_subr/translate-test$(EXEEXT) subversion/tests/libsvn_subr/utf-test$(EXEEXT) subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) subversion/tests/libsvn_wc/wc-test$(EXEEXT) subversion/tests/libsvn_delta/window-test$(EXEEXT) subversion/tests/cmdline/authz_tests.py subversion/tests/cmdline/autoprop_tests.py subversion/tests/cmdline/basic_tests.py subversion/tests/cmdline/blame_tests.py subversion/tests/cmdline/cat_tests.py subversion/tests/cmdline/changelist_tests.py subversion/tests/cmdline/checkout_tests.py subversion/tests/cmdline/commit_tests.py subversion/tests/cmdline/copy_tests.py subversion/tests/cmdline/depth_tests.py subversion/tests/cmdline/diff_tests.py subversion/tests/cmdline/entries_tests.py subversion/tests/cmdline/export_tests.py subversion/tests/cmdline/externals_tests.py subversion/tests/cmdline/getopt_tests.py subversion/tests/cmdline/history_tests.py subversion/tests/cmdline/import_tests.py subversion/tests/cmdline/info_tests.py subversion/tests/cmdline/input_validation_tests.py subversion/tests/cmdline/iprop_authz_tests.py subversion/tests/cmdline/iprop_tests.py subversion/tests/cmdline/lock_tests.py subversion/tests/cmdline/log_tests.py subversion/tests/cmdline/merge_authz_tests.py subversion/tests/cmdline/merge_automatic_tests.py subversion/tests/cmdline/merge_reintegrate_tests.py subversion/tests/cmdline/merge_tests.py subversion/tests/cmdline/merge_tree_conflict_tests.py subversion/tests/cmdline/mergeinfo_tests.py subversion/tests/cmdline/move_tests.py subversion/tests/cmdline/patch_tests.py subversion/tests/cmdline/prop_tests.py subversion/tests/cmdline/redirect_tests.py subversion/tests/cmdline/relocate_tests.py subversion/tests/cmdline/resolve_tests.py subversion/tests/cmdline/revert_tests.py subversion/tests/cmdline/schedule_tests.py subversion/tests/cmdline/special_tests.py subversion/tests/cmdline/stat_tests.py subversion/tests/cmdline/svnadmin_tests.py subversion/tests/cmdline/svnauthz_tests.py subversion/tests/cmdline/svndumpfilter_tests.py subversion/tests/cmdline/svnlook_tests.py subversion/tests/cmdline/svnmucc_tests.py subversion/tests/cmdline/svnrdump_tests.py subversion/tests/cmdline/svnsync_authz_tests.py subversion/tests/cmdline/svnsync_tests.py subversion/tests/cmdline/svnversion_tests.py subversion/tests/cmdline/switch_tests.py subversion/tests/cmdline/trans_tests.py subversion/tests/cmdline/tree_conflict_tests.py subversion/tests/cmdline/update_tests.py subversion/tests/cmdline/upgrade_tests.py subversion/tests/cmdline/wc_tests.py + +check-deps test-deps: subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) subversion/tests/cmdline/entries-dump$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT) + +MANPAGES = subversion/svn/svn.1 subversion/svnadmin/svnadmin.1 subversion/svndumpfilter/svndumpfilter.1 subversion/svnlook/svnlook.1 subversion/svnmucc/svnmucc.1 subversion/svnrdump/svnrdump.1 subversion/svnserve/svnserve.8 subversion/svnserve/svnserve.conf.5 subversion/svnsync/svnsync.1 subversion/svnversion/svnversion.1 + +CLEAN_FILES = subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT) subversion/svn/svn$(EXEEXT) subversion/svnadmin/svnadmin$(EXEEXT) subversion/svndumpfilter/svndumpfilter$(EXEEXT) subversion/svnlook/svnlook$(EXEEXT) subversion/svnmucc/svnmucc$(EXEEXT) subversion/svnrdump/svnrdump$(EXEEXT) subversion/svnserve/svnserve$(EXEEXT) subversion/svnsync/svnsync$(EXEEXT) subversion/svnversion/svnversion$(EXEEXT) subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) subversion/tests/cmdline/authz_tests.pyc subversion/tests/cmdline/autoprop_tests.pyc subversion/tests/cmdline/basic_tests.pyc subversion/tests/cmdline/blame_tests.pyc subversion/tests/cmdline/cat_tests.pyc subversion/tests/cmdline/changelist_tests.pyc subversion/tests/cmdline/checkout_tests.pyc subversion/tests/cmdline/commit_tests.pyc subversion/tests/cmdline/copy_tests.pyc subversion/tests/cmdline/depth_tests.pyc subversion/tests/cmdline/diff_tests.pyc subversion/tests/cmdline/entries-dump$(EXEEXT) subversion/tests/cmdline/entries_tests.pyc subversion/tests/cmdline/export_tests.pyc subversion/tests/cmdline/externals_tests.pyc subversion/tests/cmdline/getopt_tests.pyc subversion/tests/cmdline/history_tests.pyc subversion/tests/cmdline/import_tests.pyc subversion/tests/cmdline/info_tests.pyc subversion/tests/cmdline/input_validation_tests.pyc subversion/tests/cmdline/iprop_authz_tests.pyc subversion/tests/cmdline/iprop_tests.pyc subversion/tests/cmdline/lock_tests.pyc subversion/tests/cmdline/log_tests.pyc subversion/tests/cmdline/merge_authz_tests.pyc subversion/tests/cmdline/merge_automatic_tests.pyc subversion/tests/cmdline/merge_reintegrate_tests.pyc subversion/tests/cmdline/merge_tests.pyc subversion/tests/cmdline/merge_tree_conflict_tests.pyc subversion/tests/cmdline/mergeinfo_tests.pyc subversion/tests/cmdline/move_tests.pyc subversion/tests/cmdline/patch_tests.pyc subversion/tests/cmdline/prop_tests.pyc subversion/tests/cmdline/redirect_tests.pyc subversion/tests/cmdline/relocate_tests.pyc subversion/tests/cmdline/resolve_tests.pyc subversion/tests/cmdline/revert_tests.pyc subversion/tests/cmdline/schedule_tests.pyc subversion/tests/cmdline/special_tests.pyc subversion/tests/cmdline/stat_tests.pyc subversion/tests/cmdline/svnadmin_tests.pyc subversion/tests/cmdline/svnauthz_tests.pyc subversion/tests/cmdline/svndumpfilter_tests.pyc subversion/tests/cmdline/svnlook_tests.pyc subversion/tests/cmdline/svnmucc_tests.pyc subversion/tests/cmdline/svnrdump_tests.pyc subversion/tests/cmdline/svnsync_authz_tests.pyc subversion/tests/cmdline/svnsync_tests.pyc subversion/tests/cmdline/svnversion_tests.pyc subversion/tests/cmdline/switch_tests.pyc subversion/tests/cmdline/trans_tests.pyc subversion/tests/cmdline/tree_conflict_tests.pyc subversion/tests/cmdline/update_tests.pyc subversion/tests/cmdline/upgrade_tests.pyc subversion/tests/cmdline/wc_tests.pyc subversion/tests/libsvn_client/client-test$(EXEEXT) subversion/tests/libsvn_delta/random-test$(EXEEXT) subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) subversion/tests/libsvn_delta/window-test$(EXEEXT) subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) subversion/tests/libsvn_fs/fs-test$(EXEEXT) subversion/tests/libsvn_fs/locks-test$(EXEEXT) subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT) subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) subversion/tests/libsvn_ra/ra-test$(EXEEXT) subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) subversion/tests/libsvn_repos/repos-test$(EXEEXT) subversion/tests/libsvn_subr/auth-test$(EXEEXT) subversion/tests/libsvn_subr/cache-test$(EXEEXT) subversion/tests/libsvn_subr/checksum-test$(EXEEXT) subversion/tests/libsvn_subr/compat-test$(EXEEXT) subversion/tests/libsvn_subr/config-test$(EXEEXT) subversion/tests/libsvn_subr/crypto-test$(EXEEXT) subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) subversion/tests/libsvn_subr/error-code-test$(EXEEXT) subversion/tests/libsvn_subr/error-test$(EXEEXT) subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) subversion/tests/libsvn_subr/io-test$(EXEEXT) subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) subversion/tests/libsvn_subr/opt-test$(EXEEXT) subversion/tests/libsvn_subr/path-test$(EXEEXT) subversion/tests/libsvn_subr/revision-test$(EXEEXT) subversion/tests/libsvn_subr/skel-test$(EXEEXT) subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) subversion/tests/libsvn_subr/stream-test$(EXEEXT) subversion/tests/libsvn_subr/string-test$(EXEEXT) subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) subversion/tests/libsvn_subr/time-test$(EXEEXT) subversion/tests/libsvn_subr/translate-test$(EXEEXT) subversion/tests/libsvn_subr/utf-test$(EXEEXT) subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) subversion/tests/libsvn_wc/db-test$(EXEEXT) subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) subversion/tests/libsvn_wc/wc-test$(EXEEXT) tools/client-side/svn-bench/svn-bench$(EXEEXT) tools/dev/fsfs-access-map$(EXEEXT) tools/dev/fsfs-reorg$(EXEEXT) tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT) tools/diff/diff$(EXEEXT) tools/diff/diff3$(EXEEXT) tools/diff/diff4$(EXEEXT) tools/server-side/fsfs-stats$(EXEEXT) tools/server-side/svn-populate-node-origins-index$(EXEEXT) tools/server-side/svn-rep-sharing-stats$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) +EXTRACLEAN_FILES = subversion/libsvn_fs_fs/rep-cache-db.h subversion/libsvn_subr/internal_statements.h subversion/libsvn_wc/wc-queries.h + +SWIG_INCLUDES = -I$(abs_builddir)/subversion \ + -I$(abs_srcdir)/subversion/include \ + -I$(abs_srcdir)/subversion/bindings/swig \ + -I$(abs_srcdir)/subversion/bindings/swig/include \ + -I$(abs_srcdir)/subversion/bindings/swig/proxy \ + -I$(abs_builddir)/subversion/bindings/swig/proxy \ + $(SVN_APR_INCLUDES) $(SVN_APRUTIL_INCLUDES) + +RELEASE_MODE = 1 + + +######################################## +# Section 2: SWIG headers (wrappers and external runtimes) +######################################## + + +######################################## +# Section 3: SWIG autogen rules +######################################## + +autogen-swig-py: subversion/bindings/swig/python/core.c subversion/bindings/swig/python/svn_client.c subversion/bindings/swig/python/svn_delta.c subversion/bindings/swig/python/svn_diff.c subversion/bindings/swig/python/svn_fs.c subversion/bindings/swig/python/svn_ra.c subversion/bindings/swig/python/svn_repos.c subversion/bindings/swig/python/svn_wc.c +autogen-swig: autogen-swig-py + +autogen-swig-pl: subversion/bindings/swig/perl/native/core.c subversion/bindings/swig/perl/native/svn_client.c subversion/bindings/swig/perl/native/svn_delta.c subversion/bindings/swig/perl/native/svn_diff.c subversion/bindings/swig/perl/native/svn_fs.c subversion/bindings/swig/perl/native/svn_ra.c subversion/bindings/swig/perl/native/svn_repos.c subversion/bindings/swig/perl/native/svn_wc.c +autogen-swig: autogen-swig-pl + +autogen-swig-rb: subversion/bindings/swig/ruby/core.c subversion/bindings/swig/ruby/svn_client.c subversion/bindings/swig/ruby/svn_delta.c subversion/bindings/swig/ruby/svn_diff.c subversion/bindings/swig/ruby/svn_fs.c subversion/bindings/swig/ruby/svn_ra.c subversion/bindings/swig/ruby/svn_repos.c subversion/bindings/swig/ruby/svn_wc.c +autogen-swig: autogen-swig-rb + + + +######################################## +# Section 4: Rules to build SWIG .c files from .i files +######################################## + + +# This needs to be here, rather than in Makefile.in, else +# './autogen.sh --release' doesn't find it. +.swig_checked: + @if [ "$(SWIG)" = "none" ]; then \ + echo "SWIG disabled at configure time" >&2; \ + exit 1; \ + fi + @touch .swig_checked + + +######################################## +# Section 5: Individual target build rules +######################################## + +atomic_ra_revprop_change_PATH = subversion/tests/cmdline +atomic_ra_revprop_change_DEPS = subversion/tests/cmdline/atomic-ra-revprop-change.lo subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la +atomic_ra_revprop_change_OBJECTS = atomic-ra-revprop-change.lo +subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT): $(atomic_ra_revprop_change_DEPS) + cd subversion/tests/cmdline && $(LINK) $(atomic_ra_revprop_change_LDFLAGS) -o atomic-ra-revprop-change$(EXEEXT) $(atomic_ra_revprop_change_OBJECTS) ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +auth_test_PATH = subversion/tests/libsvn_subr +auth_test_DEPS = subversion/tests/libsvn_subr/auth-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +auth_test_OBJECTS = auth-test.lo +subversion/tests/libsvn_subr/auth-test$(EXEEXT): $(auth_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(auth_test_LDFLAGS) -o auth-test$(EXEEXT) $(auth_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +cache_test_PATH = subversion/tests/libsvn_subr +cache_test_DEPS = subversion/tests/libsvn_subr/cache-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +cache_test_OBJECTS = cache-test.lo +subversion/tests/libsvn_subr/cache-test$(EXEEXT): $(cache_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(cache_test_LDFLAGS) -o cache-test$(EXEEXT) $(cache_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +changes_test_PATH = subversion/tests/libsvn_fs_base +changes_test_DEPS = subversion/tests/libsvn_fs_base/changes-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_base/libsvn_fs_base-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +changes_test_OBJECTS = changes-test.lo +subversion/tests/libsvn_fs_base/changes-test$(EXEEXT): $(changes_test_DEPS) + cd subversion/tests/libsvn_fs_base && $(LINK) $(changes_test_LDFLAGS) -o changes-test$(EXEEXT) $(changes_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_fs_base/libsvn_fs_base-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +checksum_test_PATH = subversion/tests/libsvn_subr +checksum_test_DEPS = subversion/tests/libsvn_subr/checksum-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +checksum_test_OBJECTS = checksum-test.lo +subversion/tests/libsvn_subr/checksum-test$(EXEEXT): $(checksum_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(checksum_test_LDFLAGS) -o checksum-test$(EXEEXT) $(checksum_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +client_test_PATH = subversion/tests/libsvn_client +client_test_DEPS = subversion/tests/libsvn_client/client-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +client_test_OBJECTS = client-test.lo +subversion/tests/libsvn_client/client-test$(EXEEXT): $(client_test_DEPS) + cd subversion/tests/libsvn_client && $(LINK) $(client_test_LDFLAGS) -o client-test$(EXEEXT) $(client_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +compat_test_PATH = subversion/tests/libsvn_subr +compat_test_DEPS = subversion/tests/libsvn_subr/compat-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +compat_test_OBJECTS = compat-test.lo +subversion/tests/libsvn_subr/compat-test$(EXEEXT): $(compat_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(compat_test_LDFLAGS) -o compat-test$(EXEEXT) $(compat_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +config_test_PATH = subversion/tests/libsvn_subr +config_test_DEPS = subversion/tests/libsvn_subr/config-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +config_test_OBJECTS = config-test.lo +subversion/tests/libsvn_subr/config-test$(EXEEXT): $(config_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(config_test_LDFLAGS) -o config-test$(EXEEXT) $(config_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +conflict_data_test_PATH = subversion/tests/libsvn_wc +conflict_data_test_DEPS = subversion/tests/libsvn_wc/conflict-data-test.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +conflict_data_test_OBJECTS = conflict-data-test.lo utils.lo +subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT): $(conflict_data_test_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(conflict_data_test_LDFLAGS) -o conflict-data-test$(EXEEXT) $(conflict_data_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +crypto_test_PATH = subversion/tests/libsvn_subr +crypto_test_DEPS = subversion/tests/libsvn_subr/crypto-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +crypto_test_OBJECTS = crypto-test.lo +subversion/tests/libsvn_subr/crypto-test$(EXEEXT): $(crypto_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(crypto_test_LDFLAGS) -o crypto-test$(EXEEXT) $(crypto_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +cxxhl_tests_PATH = subversion/bindings/cxxhl +cxxhl_tests_DEPS = subversion/bindings/cxxhl/tests/test_exception.lo subversion/bindings/cxxhl/libsvncxxhl-1.la subversion/libsvn_subr/libsvn_subr-1.la +cxxhl_tests_OBJECTS = tests/test_exception.lo +subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT): $(cxxhl_tests_DEPS) + cd subversion/bindings/cxxhl && $(LINK_CXX) $(cxxhl_tests_LDFLAGS) -o cxxhl-tests$(EXEEXT) $(cxxhl_tests_OBJECTS) ../../../subversion/bindings/cxxhl/libsvncxxhl-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(LIBS) + +db_test_PATH = subversion/tests/libsvn_wc +db_test_DEPS = subversion/tests/libsvn_wc/db-test.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +db_test_OBJECTS = db-test.lo utils.lo +subversion/tests/libsvn_wc/db-test$(EXEEXT): $(db_test_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(db_test_LDFLAGS) -o db-test$(EXEEXT) $(db_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +diff_PATH = tools/diff +diff_DEPS = tools/diff/diff.lo subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +diff_OBJECTS = diff.lo +tools/diff/diff$(EXEEXT): $(diff_DEPS) + cd tools/diff && $(LINK) $(diff_LDFLAGS) -o diff$(EXEEXT) $(diff_OBJECTS) ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +diff_diff3_test_PATH = subversion/tests/libsvn_diff +diff_diff3_test_DEPS = subversion/tests/libsvn_diff/diff-diff3-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +diff_diff3_test_OBJECTS = diff-diff3-test.lo +subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT): $(diff_diff3_test_DEPS) + cd subversion/tests/libsvn_diff && $(LINK) $(diff_diff3_test_LDFLAGS) -o diff-diff3-test$(EXEEXT) $(diff_diff3_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +diff3_PATH = tools/diff +diff3_DEPS = tools/diff/diff3.lo subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +diff3_OBJECTS = diff3.lo +tools/diff/diff3$(EXEEXT): $(diff3_DEPS) + cd tools/diff && $(LINK) $(diff3_LDFLAGS) -o diff3$(EXEEXT) $(diff3_OBJECTS) ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +diff4_PATH = tools/diff +diff4_DEPS = tools/diff/diff4.lo subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +diff4_OBJECTS = diff4.lo +tools/diff/diff4$(EXEEXT): $(diff4_DEPS) + cd tools/diff && $(LINK) $(diff4_LDFLAGS) -o diff4$(EXEEXT) $(diff4_OBJECTS) ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +dirent_uri_test_PATH = subversion/tests/libsvn_subr +dirent_uri_test_DEPS = subversion/tests/libsvn_subr/dirent_uri-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +dirent_uri_test_OBJECTS = dirent_uri-test.lo +subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT): $(dirent_uri_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(dirent_uri_test_LDFLAGS) -o dirent_uri-test$(EXEEXT) $(dirent_uri_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +entries_compat_test_PATH = subversion/tests/libsvn_wc +entries_compat_test_DEPS = subversion/tests/libsvn_wc/entries-compat.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +entries_compat_test_OBJECTS = entries-compat.lo utils.lo +subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT): $(entries_compat_test_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(entries_compat_test_LDFLAGS) -o entries-compat-test$(EXEEXT) $(entries_compat_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +entries_dump_PATH = subversion/tests/cmdline +entries_dump_DEPS = subversion/tests/cmdline/entries-dump.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +entries_dump_OBJECTS = entries-dump.lo +subversion/tests/cmdline/entries-dump$(EXEEXT): $(entries_dump_DEPS) + cd subversion/tests/cmdline && $(LINK) $(entries_dump_LDFLAGS) -o entries-dump$(EXEEXT) $(entries_dump_OBJECTS) ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +error_code_test_PATH = subversion/tests/libsvn_subr +error_code_test_DEPS = subversion/tests/libsvn_subr/error-code-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +error_code_test_OBJECTS = error-code-test.lo +subversion/tests/libsvn_subr/error-code-test$(EXEEXT): $(error_code_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(error_code_test_LDFLAGS) -o error-code-test$(EXEEXT) $(error_code_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +error_test_PATH = subversion/tests/libsvn_subr +error_test_DEPS = subversion/tests/libsvn_subr/error-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +error_test_OBJECTS = error-test.lo +subversion/tests/libsvn_subr/error-test$(EXEEXT): $(error_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(error_test_LDFLAGS) -o error-test$(EXEEXT) $(error_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +fs_base_test_PATH = subversion/tests/libsvn_fs_base +fs_base_test_DEPS = subversion/tests/libsvn_fs_base/fs-base-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_base/libsvn_fs_base-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la subversion/libsvn_subr/libsvn_subr-1.la +fs_base_test_OBJECTS = fs-base-test.lo +subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT): $(fs_base_test_DEPS) + cd subversion/tests/libsvn_fs_base && $(LINK) $(fs_base_test_LDFLAGS) -o fs-base-test$(EXEEXT) $(fs_base_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_fs_base/libsvn_fs_base-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_fs_util/libsvn_fs_util-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +fs_pack_test_PATH = subversion/tests/libsvn_fs_fs +fs_pack_test_DEPS = subversion/tests/libsvn_fs_fs/fs-pack-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_fs/libsvn_fs_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +fs_pack_test_OBJECTS = fs-pack-test.lo +subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT): $(fs_pack_test_DEPS) + cd subversion/tests/libsvn_fs_fs && $(LINK) $(fs_pack_test_LDFLAGS) -o fs-pack-test$(EXEEXT) $(fs_pack_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_fs_fs/libsvn_fs_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +fs_test_PATH = subversion/tests/libsvn_fs +fs_test_DEPS = subversion/tests/libsvn_fs/fs-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +fs_test_OBJECTS = fs-test.lo +subversion/tests/libsvn_fs/fs-test$(EXEEXT): $(fs_test_DEPS) + cd subversion/tests/libsvn_fs && $(LINK) $(fs_test_LDFLAGS) -o fs-test$(EXEEXT) $(fs_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +fsfs_access_map_PATH = tools/dev +fsfs_access_map_DEPS = tools/dev/fsfs-access-map.lo subversion/libsvn_subr/libsvn_subr-1.la +fsfs_access_map_OBJECTS = fsfs-access-map.lo +tools/dev/fsfs-access-map$(EXEEXT): $(fsfs_access_map_DEPS) + cd tools/dev && $(LINK) $(fsfs_access_map_LDFLAGS) -o fsfs-access-map$(EXEEXT) $(fsfs_access_map_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +fsfs_reorg_PATH = tools/dev +fsfs_reorg_DEPS = tools/dev/fsfs-reorg.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +fsfs_reorg_OBJECTS = fsfs-reorg.lo +tools/dev/fsfs-reorg$(EXEEXT): $(fsfs_reorg_DEPS) + cd tools/dev && $(LINK) $(fsfs_reorg_LDFLAGS) -o fsfs-reorg$(EXEEXT) $(fsfs_reorg_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +fsfs_stats_PATH = tools/server-side +fsfs_stats_DEPS = tools/server-side/fsfs-stats.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +fsfs_stats_OBJECTS = fsfs-stats.lo +tools/server-side/fsfs-stats$(EXEEXT): $(fsfs_stats_DEPS) + cd tools/server-side && $(LINK) $(fsfs_stats_LDFLAGS) -o fsfs-stats$(EXEEXT) $(fsfs_stats_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +hashdump_test_PATH = subversion/tests/libsvn_subr +hashdump_test_DEPS = subversion/tests/libsvn_subr/hashdump-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +hashdump_test_OBJECTS = hashdump-test.lo +subversion/tests/libsvn_subr/hashdump-test$(EXEEXT): $(hashdump_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(hashdump_test_LDFLAGS) -o hashdump-test$(EXEEXT) $(hashdump_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +io_test_PATH = subversion/tests/libsvn_subr +io_test_DEPS = subversion/tests/libsvn_subr/io-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +io_test_OBJECTS = io-test.lo +subversion/tests/libsvn_subr/io-test$(EXEEXT): $(io_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(io_test_LDFLAGS) -o io-test$(EXEEXT) $(io_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +javahl_callback_javah_PATH = subversion/bindings/javahl/include +javahl_callback_javah_HEADERS = subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_BlameCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ChangelistCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ClientNotifyCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_CommitCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_CommitMessageCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ConflictResolverCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_DiffSummaryCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ImportFilterCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_InfoCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_InheritedProplistCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ListCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_LogMessageCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_PatchCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ProgressCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ProplistCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ReposFreezeAction.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ReposNotifyCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_StatusCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_UserPasswordCallback.h +javahl_callback_javah_OBJECTS = +javahl_callback_javah_DEPS = $(javahl_callback_javah_HEADERS) $(javahl_callback_javah_OBJECTS) $(javahl_java_DEPS) +javahl-callback-javah: $(javahl_callback_javah_DEPS) +javahl_callback_javah_CLASS_FILENAMES = subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/BlameCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ChangelistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ClientNotifyCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitMessageCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ConflictResolverCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/DiffSummaryCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ImportFilterCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InfoCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InheritedProplistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ListCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/LogMessageCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/PatchCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProgressCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProplistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposFreezeAction.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposNotifyCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/StatusCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/UserPasswordCallback.class +javahl_callback_javah_CLASSES = org.apache.subversion.javahl.callback.BlameCallback org.apache.subversion.javahl.callback.ChangelistCallback org.apache.subversion.javahl.callback.ClientNotifyCallback org.apache.subversion.javahl.callback.CommitCallback org.apache.subversion.javahl.callback.CommitMessageCallback org.apache.subversion.javahl.callback.ConflictResolverCallback org.apache.subversion.javahl.callback.DiffSummaryCallback org.apache.subversion.javahl.callback.ImportFilterCallback org.apache.subversion.javahl.callback.InfoCallback org.apache.subversion.javahl.callback.InheritedProplistCallback org.apache.subversion.javahl.callback.ListCallback org.apache.subversion.javahl.callback.LogMessageCallback org.apache.subversion.javahl.callback.PatchCallback org.apache.subversion.javahl.callback.ProgressCallback org.apache.subversion.javahl.callback.ProplistCallback org.apache.subversion.javahl.callback.ReposFreezeAction org.apache.subversion.javahl.callback.ReposNotifyCallback org.apache.subversion.javahl.callback.StatusCallback org.apache.subversion.javahl.callback.UserPasswordCallback +$(javahl_callback_javah_HEADERS): $(javahl_callback_javah_CLASS_FILENAMES) + $(COMPILE_JAVAHL_JAVAH) -force -d subversion/bindings/javahl/include -classpath subversion/bindings/javahl/classes:$(javahl_callback_javah_CLASSPATH) $(javahl_callback_javah_CLASSES) + + +javahl_compat_java_PATH = subversion/bindings/javahl/classes +javahl_compat_java_HEADERS = +javahl_compat_java_OBJECTS = subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback2.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback3.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallbackImpl.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ChangePath.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ChangelistCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ClientException.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitItem.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitItemStateFlags.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitMessage.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictDescriptor.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictResolverCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictResult.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictVersion.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CopySource.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Depth.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DiffSummary.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DiffSummaryReceiver.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DirEntry.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ErrorCodes.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Info.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Info2.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/InfoCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/InputInterface.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ListCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Lock.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LockStatus.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogDate.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogMessage.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogMessageCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Mergeinfo.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/MergeinfoLogKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NativeException.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NodeKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Notify.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Notify2.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyAction.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyInformation.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyStatus.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Operation.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/OutputInterface.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Path.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProgressEvent.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProgressListener.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword2.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword3.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PropertyData.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProplistCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProplistCallbackImpl.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Revision.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RevisionKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RevisionRange.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNAdmin.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClient.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientInterface.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientLogLevel.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientSynchronized.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNInputStream.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNOutputStream.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ScheduleKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Status.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/StatusCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/StatusKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SubversionException.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Version.class +javahl_compat_java_DEPS = $(javahl_compat_java_HEADERS) $(javahl_compat_java_OBJECTS) $(javahl_java_DEPS) +javahl-compat-java: $(javahl_compat_java_DEPS) +javahl_compat_java_SRC = $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback2.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback3.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallbackImpl.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ChangePath.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ChangelistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ClientException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitItem.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitItemStateFlags.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitMessage.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictDescriptor.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictResolverCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictResult.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictVersion.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/CopySource.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Depth.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/DiffSummary.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/DiffSummaryReceiver.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/DirEntry.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ErrorCodes.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Info.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Info2.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/InfoCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/InputInterface.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ListCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Lock.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/LockStatus.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogDate.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogMessage.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogMessageCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Mergeinfo.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/MergeinfoLogKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NativeException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NodeKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Notify.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Notify2.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyAction.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyInformation.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyStatus.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Operation.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/OutputInterface.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Path.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProgressEvent.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProgressListener.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword2.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword3.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/PropertyData.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Revision.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/RevisionKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/RevisionRange.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNAdmin.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientLogLevel.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNInputStream.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNOutputStream.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ScheduleKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Status.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/StatusCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/StatusKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SubversionException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Version.java +$(javahl_compat_java_OBJECTS): $(javahl_compat_java_SRC) + $(COMPILE_JAVAHL_JAVAC) -d subversion/bindings/javahl/classes -classpath subversion/bindings/javahl/classes:$(javahl_compat_java_CLASSPATH) $(javahl_compat_java_SRC) + + +javahl_compat_tests_PATH = subversion/bindings/javahl/classes +javahl_compat_tests_HEADERS = +javahl_compat_tests_OBJECTS = subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BasicTests.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RunTests.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNAdminTests.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNTests.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/WC.class +javahl_compat_tests_DEPS = $(javahl_compat_tests_HEADERS) $(javahl_compat_tests_OBJECTS) $(javahl_compat_java_DEPS) +javahl-compat-tests: $(javahl_compat_tests_DEPS) +javahl_compat_tests_SRC = $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/BasicTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/RunTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/SVNAdminTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/SVNTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/WC.java +$(javahl_compat_tests_OBJECTS): $(javahl_compat_tests_SRC) + $(COMPILE_JAVAHL_JAVAC) -d subversion/bindings/javahl/classes -classpath subversion/bindings/javahl/classes:$(javahl_compat_tests_CLASSPATH) $(javahl_compat_tests_SRC) + + +javahl_java_PATH = subversion/bindings/javahl/classes +javahl_java_HEADERS = +javahl_java_OBJECTS = subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientNotifyInformation.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitInfo.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItem.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItemStateFlags.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictDescriptor.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictResult.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/DiffSummary.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNClient.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNRepos.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/JNIError.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeResources.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ProgressEvent.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ReposNotifyInformation.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNClient.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNRepos.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SubversionException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/BlameCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ChangelistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ClientNotifyCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitMessageCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ConflictResolverCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/DiffSummaryCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ImportFilterCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InfoCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InheritedProplistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ListCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/LogMessageCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/PatchCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProgressCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProplistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposFreezeAction.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposNotifyCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/StatusCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/UserPasswordCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ChangePath.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Checksum.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ConflictVersion.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/CopySource.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Depth.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DiffOptions.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DirEntry.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Info.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Lock.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/LogDate.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Mergeinfo.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/NodeKind.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Property.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Revision.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/RevisionRange.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Status.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Tristate.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Version.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/VersionExtended.class +javahl_java_DEPS = $(javahl_java_HEADERS) $(javahl_java_OBJECTS) +javahl-java: $(javahl_java_DEPS) +javahl_java_SRC = $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitInfo.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitItem.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitItemStateFlags.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictDescriptor.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictResult.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/DiffSummary.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNClient.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNRepos.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/JNIError.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeResources.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ProgressEvent.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ReposNotifyInformation.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNClient.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNRepos.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/SubversionException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ChangelistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ClientNotifyCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/CommitCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/CommitMessageCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ConflictResolverCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/DiffSummaryCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ImportFilterCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/InfoCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/InheritedProplistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ListCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/PatchCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProgressCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ReposFreezeAction.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ReposNotifyCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/StatusCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/UserPasswordCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/ChangePath.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Checksum.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/ConflictVersion.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/CopySource.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Depth.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/DiffOptions.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/DirEntry.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Info.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Lock.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/LogDate.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Mergeinfo.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/NodeKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Property.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Revision.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/RevisionRange.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Status.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Tristate.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Version.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/VersionExtended.java +$(javahl_java_OBJECTS): $(javahl_java_SRC) + $(COMPILE_JAVAHL_JAVAC) -d subversion/bindings/javahl/classes -classpath subversion/bindings/javahl/classes:$(javahl_java_CLASSPATH) $(javahl_java_SRC) + + +javahl_javah_PATH = subversion/bindings/javahl/include +javahl_javah_HEADERS = subversion/bindings/javahl/include/org_apache_subversion_javahl_ClientException.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ClientNotifyInformation.h subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitInfo.h subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItem.h subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItemStateFlags.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ConflictDescriptor.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ConflictResult.h subversion/bindings/javahl/include/org_apache_subversion_javahl_DiffSummary.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ISVNClient.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ISVNRepos.h subversion/bindings/javahl/include/org_apache_subversion_javahl_JNIError.h subversion/bindings/javahl/include/org_apache_subversion_javahl_NativeException.h subversion/bindings/javahl/include/org_apache_subversion_javahl_NativeResources.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ProgressEvent.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ReposNotifyInformation.h subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNClient.h subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNRepos.h subversion/bindings/javahl/include/org_apache_subversion_javahl_SubversionException.h +javahl_javah_OBJECTS = +javahl_javah_DEPS = $(javahl_javah_HEADERS) $(javahl_javah_OBJECTS) $(javahl_java_DEPS) +javahl-javah: $(javahl_javah_DEPS) +javahl_javah_CLASS_FILENAMES = subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientNotifyInformation.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitInfo.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItem.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItemStateFlags.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictDescriptor.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictResult.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/DiffSummary.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNClient.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNRepos.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/JNIError.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeResources.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ProgressEvent.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ReposNotifyInformation.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNClient.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNRepos.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SubversionException.class +javahl_javah_CLASSES = org.apache.subversion.javahl.ClientException org.apache.subversion.javahl.ClientNotifyInformation org.apache.subversion.javahl.CommitInfo org.apache.subversion.javahl.CommitItem org.apache.subversion.javahl.CommitItemStateFlags org.apache.subversion.javahl.ConflictDescriptor org.apache.subversion.javahl.ConflictResult org.apache.subversion.javahl.DiffSummary org.apache.subversion.javahl.ISVNClient org.apache.subversion.javahl.ISVNRepos org.apache.subversion.javahl.JNIError org.apache.subversion.javahl.NativeException org.apache.subversion.javahl.NativeResources org.apache.subversion.javahl.ProgressEvent org.apache.subversion.javahl.ReposNotifyInformation org.apache.subversion.javahl.SVNClient org.apache.subversion.javahl.SVNRepos org.apache.subversion.javahl.SubversionException +$(javahl_javah_HEADERS): $(javahl_javah_CLASS_FILENAMES) + $(COMPILE_JAVAHL_JAVAH) -force -d subversion/bindings/javahl/include -classpath subversion/bindings/javahl/classes:$(javahl_javah_CLASSPATH) $(javahl_javah_CLASSES) + + +javahl_tests_PATH = subversion/bindings/javahl/classes +javahl_tests_HEADERS = +javahl_tests_OBJECTS = subversion/bindings/javahl/classes/org/apache/subversion/javahl/BasicTests.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/RunTests.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNReposTests.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNTests.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/WC.class +javahl_tests_DEPS = $(javahl_tests_HEADERS) $(javahl_tests_OBJECTS) $(javahl_java_DEPS) +javahl-tests: $(javahl_tests_DEPS) +javahl_tests_SRC = $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/RunTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/SVNReposTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/SVNTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/WC.java +$(javahl_tests_OBJECTS): $(javahl_tests_SRC) + $(COMPILE_JAVAHL_JAVAC) -d subversion/bindings/javahl/classes -classpath subversion/bindings/javahl/classes:$(javahl_tests_CLASSPATH) $(javahl_tests_SRC) + + +javahl_types_javah_PATH = subversion/bindings/javahl/include +javahl_types_javah_HEADERS = subversion/bindings/javahl/include/org_apache_subversion_javahl_types_ChangePath.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Checksum.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_ConflictVersion.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_CopySource.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Depth.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_DiffOptions.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_DirEntry.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Info.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Lock.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_LogDate.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Mergeinfo.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_NodeKind.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Property.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Revision.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_RevisionRange.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Status.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Tristate.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Version.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended.h +javahl_types_javah_OBJECTS = +javahl_types_javah_DEPS = $(javahl_types_javah_HEADERS) $(javahl_types_javah_OBJECTS) $(javahl_java_DEPS) +javahl-types-javah: $(javahl_types_javah_DEPS) +javahl_types_javah_CLASS_FILENAMES = subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ChangePath.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Checksum.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ConflictVersion.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/CopySource.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Depth.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DiffOptions.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DirEntry.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Info.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Lock.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/LogDate.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Mergeinfo.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/NodeKind.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Property.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Revision.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/RevisionRange.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Status.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Tristate.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Version.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/VersionExtended.class +javahl_types_javah_CLASSES = org.apache.subversion.javahl.types.ChangePath org.apache.subversion.javahl.types.Checksum org.apache.subversion.javahl.types.ConflictVersion org.apache.subversion.javahl.types.CopySource org.apache.subversion.javahl.types.Depth org.apache.subversion.javahl.types.DiffOptions org.apache.subversion.javahl.types.DirEntry org.apache.subversion.javahl.types.Info org.apache.subversion.javahl.types.Lock org.apache.subversion.javahl.types.LogDate org.apache.subversion.javahl.types.Mergeinfo org.apache.subversion.javahl.types.NodeKind org.apache.subversion.javahl.types.Property org.apache.subversion.javahl.types.Revision org.apache.subversion.javahl.types.RevisionRange org.apache.subversion.javahl.types.Status org.apache.subversion.javahl.types.Tristate org.apache.subversion.javahl.types.Version org.apache.subversion.javahl.types.VersionExtended +$(javahl_types_javah_HEADERS): $(javahl_types_javah_CLASS_FILENAMES) + $(COMPILE_JAVAHL_JAVAH) -force -d subversion/bindings/javahl/include -classpath subversion/bindings/javahl/classes:$(javahl_types_javah_CLASSPATH) $(javahl_types_javah_CLASSES) + + +libsvn_auth_gnome_keyring_PATH = subversion/libsvn_auth_gnome_keyring +libsvn_auth_gnome_keyring_DEPS = subversion/libsvn_auth_gnome_keyring/gnome_keyring.lo subversion/libsvn_auth_gnome_keyring/version.lo subversion/libsvn_subr/libsvn_subr-1.la +libsvn_auth_gnome_keyring_OBJECTS = gnome_keyring.lo version.lo +subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring-1.la: $(libsvn_auth_gnome_keyring_DEPS) + cd subversion/libsvn_auth_gnome_keyring && $(LINK_LIB) $(libsvn_auth_gnome_keyring_LDFLAGS) -o libsvn_auth_gnome_keyring-1.la $(LT_NO_UNDEFINED) $(libsvn_auth_gnome_keyring_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(SVN_GNOME_KEYRING_LIBS) $(LIBS) + +libsvn_auth_kwallet_PATH = subversion/libsvn_auth_kwallet +libsvn_auth_kwallet_DEPS = subversion/libsvn_auth_kwallet/kwallet.lo subversion/libsvn_auth_kwallet/version.lo subversion/libsvn_subr/libsvn_subr-1.la +libsvn_auth_kwallet_OBJECTS = kwallet.lo version.lo +subversion/libsvn_auth_kwallet/libsvn_auth_kwallet-1.la: $(libsvn_auth_kwallet_DEPS) + cd subversion/libsvn_auth_kwallet && $(LINK_CXX_LIB) $(libsvn_auth_kwallet_LDFLAGS) -o libsvn_auth_kwallet-1.la $(LT_NO_UNDEFINED) $(libsvn_auth_kwallet_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(SVN_KWALLET_LIBS) $(LIBS) + +libsvn_client_PATH = subversion/libsvn_client +libsvn_client_DEPS = subversion/libsvn_client/add.lo subversion/libsvn_client/blame.lo subversion/libsvn_client/cat.lo subversion/libsvn_client/changelist.lo subversion/libsvn_client/checkout.lo subversion/libsvn_client/cleanup.lo subversion/libsvn_client/cmdline.lo subversion/libsvn_client/commit.lo subversion/libsvn_client/commit_util.lo subversion/libsvn_client/compat_providers.lo subversion/libsvn_client/copy.lo subversion/libsvn_client/copy_foreign.lo subversion/libsvn_client/ctx.lo subversion/libsvn_client/delete.lo subversion/libsvn_client/deprecated.lo subversion/libsvn_client/diff.lo subversion/libsvn_client/diff_local.lo subversion/libsvn_client/diff_summarize.lo subversion/libsvn_client/export.lo subversion/libsvn_client/externals.lo subversion/libsvn_client/import.lo subversion/libsvn_client/info.lo subversion/libsvn_client/iprops.lo subversion/libsvn_client/list.lo subversion/libsvn_client/locking_commands.lo subversion/libsvn_client/log.lo subversion/libsvn_client/merge.lo subversion/libsvn_client/mergeinfo.lo subversion/libsvn_client/patch.lo subversion/libsvn_client/prop_commands.lo subversion/libsvn_client/ra.lo subversion/libsvn_client/relocate.lo subversion/libsvn_client/repos_diff.lo subversion/libsvn_client/resolved.lo subversion/libsvn_client/revert.lo subversion/libsvn_client/revisions.lo subversion/libsvn_client/status.lo subversion/libsvn_client/switch.lo subversion/libsvn_client/update.lo subversion/libsvn_client/upgrade.lo subversion/libsvn_client/url.lo subversion/libsvn_client/util.lo subversion/libsvn_client/version.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_client_OBJECTS = add.lo blame.lo cat.lo changelist.lo checkout.lo cleanup.lo cmdline.lo commit.lo commit_util.lo compat_providers.lo copy.lo copy_foreign.lo ctx.lo delete.lo deprecated.lo diff.lo diff_local.lo diff_summarize.lo export.lo externals.lo import.lo info.lo iprops.lo list.lo locking_commands.lo log.lo merge.lo mergeinfo.lo patch.lo prop_commands.lo ra.lo relocate.lo repos_diff.lo resolved.lo revert.lo revisions.lo status.lo switch.lo update.lo upgrade.lo url.lo util.lo version.lo +subversion/libsvn_client/libsvn_client-1.la: $(libsvn_client_DEPS) + cd subversion/libsvn_client && $(LINK_LIB) $(libsvn_client_LDFLAGS) -o libsvn_client-1.la $(LT_NO_UNDEFINED) $(libsvn_client_OBJECTS) ../../subversion/libsvn_wc/libsvn_wc-1.la ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_delta_PATH = subversion/libsvn_delta +libsvn_delta_DEPS = subversion/libsvn_delta/cancel.lo subversion/libsvn_delta/compat.lo subversion/libsvn_delta/compose_delta.lo subversion/libsvn_delta/debug_editor.lo subversion/libsvn_delta/default_editor.lo subversion/libsvn_delta/deprecated.lo subversion/libsvn_delta/depth_filter_editor.lo subversion/libsvn_delta/editor.lo subversion/libsvn_delta/path_driver.lo subversion/libsvn_delta/svndiff.lo subversion/libsvn_delta/text_delta.lo subversion/libsvn_delta/version.lo subversion/libsvn_delta/xdelta.lo subversion/libsvn_subr/libsvn_subr-1.la +libsvn_delta_OBJECTS = cancel.lo compat.lo compose_delta.lo debug_editor.lo default_editor.lo deprecated.lo depth_filter_editor.lo editor.lo path_driver.lo svndiff.lo text_delta.lo version.lo xdelta.lo +subversion/libsvn_delta/libsvn_delta-1.la: $(libsvn_delta_DEPS) + cd subversion/libsvn_delta && $(LINK_LIB) $(libsvn_delta_LDFLAGS) -o libsvn_delta-1.la $(LT_NO_UNDEFINED) $(libsvn_delta_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_ZLIB_LIBS) $(LIBS) + +libsvn_diff_PATH = subversion/libsvn_diff +libsvn_diff_DEPS = subversion/libsvn_diff/deprecated.lo subversion/libsvn_diff/diff.lo subversion/libsvn_diff/diff3.lo subversion/libsvn_diff/diff4.lo subversion/libsvn_diff/diff_file.lo subversion/libsvn_diff/diff_memory.lo subversion/libsvn_diff/diff_tree.lo subversion/libsvn_diff/lcs.lo subversion/libsvn_diff/parse-diff.lo subversion/libsvn_diff/token.lo subversion/libsvn_diff/util.lo subversion/libsvn_subr/libsvn_subr-1.la +libsvn_diff_OBJECTS = deprecated.lo diff.lo diff3.lo diff4.lo diff_file.lo diff_memory.lo diff_tree.lo lcs.lo parse-diff.lo token.lo util.lo +subversion/libsvn_diff/libsvn_diff-1.la: $(libsvn_diff_DEPS) + cd subversion/libsvn_diff && $(LINK_LIB) $(libsvn_diff_LDFLAGS) -o libsvn_diff-1.la $(LT_NO_UNDEFINED) $(libsvn_diff_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_ZLIB_LIBS) $(LIBS) + +libsvn_fs_PATH = subversion/libsvn_fs +install-ramod-lib: $(SVN_FS_LIB_INSTALL_DEPS) +libsvn_fs_DEPS = $(SVN_FS_LIB_DEPS) subversion/libsvn_fs/access.lo subversion/libsvn_fs/editor.lo subversion/libsvn_fs/fs-loader.lo subversion/libsvn_fs_util/libsvn_fs_util-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_fs_OBJECTS = access.lo editor.lo fs-loader.lo +subversion/libsvn_fs/libsvn_fs-1.la: $(libsvn_fs_DEPS) + cd subversion/libsvn_fs && $(LINK_LIB) $(libsvn_fs_LDFLAGS) -o libsvn_fs-1.la $(LT_NO_UNDEFINED) $(libsvn_fs_OBJECTS) ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_FS_LIB_LINK) $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_fs_base_PATH = subversion/libsvn_fs_base +libsvn_fs_base_DEPS = subversion/libsvn_fs_base/bdb/bdb-err.lo subversion/libsvn_fs_base/bdb/bdb_compat.lo subversion/libsvn_fs_base/bdb/changes-table.lo subversion/libsvn_fs_base/bdb/checksum-reps-table.lo subversion/libsvn_fs_base/bdb/copies-table.lo subversion/libsvn_fs_base/bdb/dbt.lo subversion/libsvn_fs_base/bdb/env.lo subversion/libsvn_fs_base/bdb/lock-tokens-table.lo subversion/libsvn_fs_base/bdb/locks-table.lo subversion/libsvn_fs_base/bdb/miscellaneous-table.lo subversion/libsvn_fs_base/bdb/node-origins-table.lo subversion/libsvn_fs_base/bdb/nodes-table.lo subversion/libsvn_fs_base/bdb/reps-table.lo subversion/libsvn_fs_base/bdb/rev-table.lo subversion/libsvn_fs_base/bdb/strings-table.lo subversion/libsvn_fs_base/bdb/txn-table.lo subversion/libsvn_fs_base/bdb/uuids-table.lo subversion/libsvn_fs_base/dag.lo subversion/libsvn_fs_base/err.lo subversion/libsvn_fs_base/fs.lo subversion/libsvn_fs_base/id.lo subversion/libsvn_fs_base/key-gen.lo subversion/libsvn_fs_base/lock.lo subversion/libsvn_fs_base/node-rev.lo subversion/libsvn_fs_base/reps-strings.lo subversion/libsvn_fs_base/revs-txns.lo subversion/libsvn_fs_base/trail.lo subversion/libsvn_fs_base/tree.lo subversion/libsvn_fs_base/util/fs_skels.lo subversion/libsvn_fs_base/uuid.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la +libsvn_fs_base_OBJECTS = bdb/bdb-err.lo bdb/bdb_compat.lo bdb/changes-table.lo bdb/checksum-reps-table.lo bdb/copies-table.lo bdb/dbt.lo bdb/env.lo bdb/lock-tokens-table.lo bdb/locks-table.lo bdb/miscellaneous-table.lo bdb/node-origins-table.lo bdb/nodes-table.lo bdb/reps-table.lo bdb/rev-table.lo bdb/strings-table.lo bdb/txn-table.lo bdb/uuids-table.lo dag.lo err.lo fs.lo id.lo key-gen.lo lock.lo node-rev.lo reps-strings.lo revs-txns.lo trail.lo tree.lo util/fs_skels.lo uuid.lo +subversion/libsvn_fs_base/libsvn_fs_base-1.la: $(libsvn_fs_base_DEPS) + cd subversion/libsvn_fs_base && $(LINK_LIB) $(libsvn_fs_base_LDFLAGS) -o libsvn_fs_base-1.la $(LT_NO_UNDEFINED) $(libsvn_fs_base_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_DB_LIBS) ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la $(LIBS) + +libsvn_fs_fs_PATH = subversion/libsvn_fs_fs +libsvn_fs_fs_DEPS = subversion/libsvn_fs_fs/caching.lo subversion/libsvn_fs_fs/dag.lo subversion/libsvn_fs_fs/fs.lo subversion/libsvn_fs_fs/fs_fs.lo subversion/libsvn_fs_fs/id.lo subversion/libsvn_fs_fs/key-gen.lo subversion/libsvn_fs_fs/lock.lo subversion/libsvn_fs_fs/rep-cache.lo subversion/libsvn_fs_fs/temp_serializer.lo subversion/libsvn_fs_fs/tree.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la +libsvn_fs_fs_OBJECTS = caching.lo dag.lo fs.lo fs_fs.lo id.lo key-gen.lo lock.lo rep-cache.lo temp_serializer.lo tree.lo +subversion/libsvn_fs_fs/libsvn_fs_fs-1.la: $(libsvn_fs_fs_DEPS) + cd subversion/libsvn_fs_fs && $(LINK_LIB) $(libsvn_fs_fs_LDFLAGS) -o libsvn_fs_fs-1.la $(LT_NO_UNDEFINED) $(libsvn_fs_fs_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la $(LIBS) + +libsvn_fs_util_PATH = subversion/libsvn_fs_util +libsvn_fs_util_DEPS = subversion/libsvn_fs_util/fs-util.lo subversion/libsvn_subr/libsvn_subr-1.la +libsvn_fs_util_OBJECTS = fs-util.lo +subversion/libsvn_fs_util/libsvn_fs_util-1.la: $(libsvn_fs_util_DEPS) + cd subversion/libsvn_fs_util && $(LINK_LIB) $(libsvn_fs_util_LDFLAGS) -o libsvn_fs_util-1.la $(LT_NO_UNDEFINED) $(libsvn_fs_util_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_ra_PATH = subversion/libsvn_ra +install-lib: $(SVN_RA_LIB_INSTALL_DEPS) +libsvn_ra_DEPS = $(SVN_RA_LIB_DEPS) subversion/libsvn_ra/compat.lo subversion/libsvn_ra/debug_reporter.lo subversion/libsvn_ra/deprecated.lo subversion/libsvn_ra/editor.lo subversion/libsvn_ra/ra_loader.lo subversion/libsvn_ra/util.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_ra_OBJECTS = compat.lo debug_reporter.lo deprecated.lo editor.lo ra_loader.lo util.lo +subversion/libsvn_ra/libsvn_ra-1.la: $(libsvn_ra_DEPS) + cd subversion/libsvn_ra && $(LINK_LIB) $(libsvn_ra_LDFLAGS) -o libsvn_ra-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_RA_LIB_LINK) $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_ra_local_PATH = subversion/libsvn_ra_local +libsvn_ra_local_DEPS = subversion/libsvn_ra_local/ra_plugin.lo subversion/libsvn_ra_local/split_url.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_ra_local_OBJECTS = ra_plugin.lo split_url.lo +subversion/libsvn_ra_local/libsvn_ra_local-1.la: $(libsvn_ra_local_DEPS) + cd subversion/libsvn_ra_local && $(LINK_LIB) $(libsvn_ra_local_LDFLAGS) -o libsvn_ra_local-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_local_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_ra_serf_PATH = subversion/libsvn_ra_serf +libsvn_ra_serf_DEPS = subversion/libsvn_ra_serf/blame.lo subversion/libsvn_ra_serf/blncache.lo subversion/libsvn_ra_serf/commit.lo subversion/libsvn_ra_serf/get_deleted_rev.lo subversion/libsvn_ra_serf/getdate.lo subversion/libsvn_ra_serf/getlocations.lo subversion/libsvn_ra_serf/getlocationsegments.lo subversion/libsvn_ra_serf/getlocks.lo subversion/libsvn_ra_serf/inherited_props.lo subversion/libsvn_ra_serf/locks.lo subversion/libsvn_ra_serf/log.lo subversion/libsvn_ra_serf/merge.lo subversion/libsvn_ra_serf/mergeinfo.lo subversion/libsvn_ra_serf/options.lo subversion/libsvn_ra_serf/property.lo subversion/libsvn_ra_serf/replay.lo subversion/libsvn_ra_serf/sb_bucket.lo subversion/libsvn_ra_serf/serf.lo subversion/libsvn_ra_serf/update.lo subversion/libsvn_ra_serf/util.lo subversion/libsvn_ra_serf/util_error.lo subversion/libsvn_ra_serf/xml.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_ra_serf_OBJECTS = blame.lo blncache.lo commit.lo get_deleted_rev.lo getdate.lo getlocations.lo getlocationsegments.lo getlocks.lo inherited_props.lo locks.lo log.lo merge.lo mergeinfo.lo options.lo property.lo replay.lo sb_bucket.lo serf.lo update.lo util.lo util_error.lo xml.lo +subversion/libsvn_ra_serf/libsvn_ra_serf-1.la: $(libsvn_ra_serf_DEPS) + cd subversion/libsvn_ra_serf && $(LINK_LIB) $(libsvn_ra_serf_LDFLAGS) -o libsvn_ra_serf-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_serf_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_SERF_LIBS) $(SVN_XML_LIBS) $(LIBS) + +libsvn_ra_svn_PATH = subversion/libsvn_ra_svn +libsvn_ra_svn_DEPS = subversion/libsvn_ra_svn/client.lo subversion/libsvn_ra_svn/cram.lo subversion/libsvn_ra_svn/cyrus_auth.lo subversion/libsvn_ra_svn/deprecated.lo subversion/libsvn_ra_svn/editorp.lo subversion/libsvn_ra_svn/internal_auth.lo subversion/libsvn_ra_svn/marshal.lo subversion/libsvn_ra_svn/streams.lo subversion/libsvn_ra_svn/version.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_ra_svn_OBJECTS = client.lo cram.lo cyrus_auth.lo deprecated.lo editorp.lo internal_auth.lo marshal.lo streams.lo version.lo +subversion/libsvn_ra_svn/libsvn_ra_svn-1.la: $(libsvn_ra_svn_DEPS) + cd subversion/libsvn_ra_svn && $(LINK_LIB) $(libsvn_ra_svn_LDFLAGS) -o libsvn_ra_svn-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_svn_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_SASL_LIBS) $(LIBS) + +libsvn_repos_PATH = subversion/libsvn_repos +libsvn_repos_DEPS = subversion/libsvn_repos/authz.lo subversion/libsvn_repos/commit.lo subversion/libsvn_repos/delta.lo subversion/libsvn_repos/deprecated.lo subversion/libsvn_repos/dump.lo subversion/libsvn_repos/fs-wrap.lo subversion/libsvn_repos/hooks.lo subversion/libsvn_repos/load-fs-vtable.lo subversion/libsvn_repos/load.lo subversion/libsvn_repos/log.lo subversion/libsvn_repos/node_tree.lo subversion/libsvn_repos/notify.lo subversion/libsvn_repos/replay.lo subversion/libsvn_repos/reporter.lo subversion/libsvn_repos/repos.lo subversion/libsvn_repos/rev_hunt.lo subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_repos_OBJECTS = authz.lo commit.lo delta.lo deprecated.lo dump.lo fs-wrap.lo hooks.lo load-fs-vtable.lo load.lo log.lo node_tree.lo notify.lo replay.lo reporter.lo repos.lo rev_hunt.lo +subversion/libsvn_repos/libsvn_repos-1.la: $(libsvn_repos_DEPS) + cd subversion/libsvn_repos && $(LINK_LIB) $(libsvn_repos_LDFLAGS) -o libsvn_repos-1.la $(LT_NO_UNDEFINED) $(libsvn_repos_OBJECTS) ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_subr_PATH = subversion/libsvn_subr +libsvn_subr_DEPS = subversion/libsvn_subr/adler32.lo subversion/libsvn_subr/atomic.lo subversion/libsvn_subr/auth.lo subversion/libsvn_subr/base64.lo subversion/libsvn_subr/cache-inprocess.lo subversion/libsvn_subr/cache-membuffer.lo subversion/libsvn_subr/cache-memcache.lo subversion/libsvn_subr/cache.lo subversion/libsvn_subr/cache_config.lo subversion/libsvn_subr/checksum.lo subversion/libsvn_subr/cmdline.lo subversion/libsvn_subr/compat.lo subversion/libsvn_subr/config.lo subversion/libsvn_subr/config_auth.lo subversion/libsvn_subr/config_file.lo subversion/libsvn_subr/config_win.lo subversion/libsvn_subr/crypto.lo subversion/libsvn_subr/ctype.lo subversion/libsvn_subr/date.lo subversion/libsvn_subr/debug.lo subversion/libsvn_subr/deprecated.lo subversion/libsvn_subr/dirent_uri.lo subversion/libsvn_subr/dso.lo subversion/libsvn_subr/eol.lo subversion/libsvn_subr/error.lo subversion/libsvn_subr/gpg_agent.lo subversion/libsvn_subr/hash.lo subversion/libsvn_subr/io.lo subversion/libsvn_subr/iter.lo subversion/libsvn_subr/lock.lo subversion/libsvn_subr/log.lo subversion/libsvn_subr/macos_keychain.lo subversion/libsvn_subr/magic.lo subversion/libsvn_subr/md5.lo subversion/libsvn_subr/mergeinfo.lo subversion/libsvn_subr/mutex.lo subversion/libsvn_subr/named_atomic.lo subversion/libsvn_subr/nls.lo subversion/libsvn_subr/opt.lo subversion/libsvn_subr/path.lo subversion/libsvn_subr/pool.lo subversion/libsvn_subr/prompt.lo subversion/libsvn_subr/properties.lo subversion/libsvn_subr/pseudo_md5.lo subversion/libsvn_subr/quoprint.lo subversion/libsvn_subr/sha1.lo subversion/libsvn_subr/simple_providers.lo subversion/libsvn_subr/skel.lo subversion/libsvn_subr/sorts.lo subversion/libsvn_subr/spillbuf.lo subversion/libsvn_subr/sqlite.lo subversion/libsvn_subr/sqlite3wrapper.lo subversion/libsvn_subr/ssl_client_cert_providers.lo subversion/libsvn_subr/ssl_client_cert_pw_providers.lo subversion/libsvn_subr/ssl_server_trust_providers.lo subversion/libsvn_subr/stream.lo subversion/libsvn_subr/string.lo subversion/libsvn_subr/subst.lo subversion/libsvn_subr/sysinfo.lo subversion/libsvn_subr/target.lo subversion/libsvn_subr/temp_serializer.lo subversion/libsvn_subr/time.lo subversion/libsvn_subr/token.lo subversion/libsvn_subr/types.lo subversion/libsvn_subr/user.lo subversion/libsvn_subr/username_providers.lo subversion/libsvn_subr/utf.lo subversion/libsvn_subr/utf_validate.lo subversion/libsvn_subr/utf_width.lo subversion/libsvn_subr/validate.lo subversion/libsvn_subr/version.lo subversion/libsvn_subr/win32_crashrpt.lo subversion/libsvn_subr/win32_crypto.lo subversion/libsvn_subr/win32_xlate.lo subversion/libsvn_subr/xml.lo +libsvn_subr_OBJECTS = adler32.lo atomic.lo auth.lo base64.lo cache-inprocess.lo cache-membuffer.lo cache-memcache.lo cache.lo cache_config.lo checksum.lo cmdline.lo compat.lo config.lo config_auth.lo config_file.lo config_win.lo crypto.lo ctype.lo date.lo debug.lo deprecated.lo dirent_uri.lo dso.lo eol.lo error.lo gpg_agent.lo hash.lo io.lo iter.lo lock.lo log.lo macos_keychain.lo magic.lo md5.lo mergeinfo.lo mutex.lo named_atomic.lo nls.lo opt.lo path.lo pool.lo prompt.lo properties.lo pseudo_md5.lo quoprint.lo sha1.lo simple_providers.lo skel.lo sorts.lo spillbuf.lo sqlite.lo sqlite3wrapper.lo ssl_client_cert_providers.lo ssl_client_cert_pw_providers.lo ssl_server_trust_providers.lo stream.lo string.lo subst.lo sysinfo.lo target.lo temp_serializer.lo time.lo token.lo types.lo user.lo username_providers.lo utf.lo utf_validate.lo utf_width.lo validate.lo version.lo win32_crashrpt.lo win32_crypto.lo win32_xlate.lo xml.lo +subversion/libsvn_subr/libsvn_subr-1.la: $(libsvn_subr_DEPS) + cd subversion/libsvn_subr && $(LINK_LIB) $(libsvn_subr_LDFLAGS) -o libsvn_subr-1.la $(LT_NO_UNDEFINED) $(libsvn_subr_OBJECTS) $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_XML_LIBS) $(SVN_ZLIB_LIBS) $(SVN_APR_MEMCACHE_LIBS) $(SVN_SQLITE_LIBS) $(SVN_MAGIC_LIBS) $(LIBS) + +libsvn_swig_perl_PATH = subversion/bindings/swig/perl/libsvn_swig_perl +libsvn_swig_perl_DEPS = subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_swig_perl_OBJECTS = swigutil_pl.lo +subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la: $(libsvn_swig_perl_DEPS) + cd subversion/bindings/swig/perl/libsvn_swig_perl && $(LINK_LIB) $(libsvn_swig_perl_LDFLAGS) -o libsvn_swig_perl-1.la $(LT_NO_UNDEFINED) $(libsvn_swig_perl_OBJECTS) ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_swig_py_PATH = subversion/bindings/swig/python/libsvn_swig_py +libsvn_swig_py_DEPS = subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_swig_py_OBJECTS = swigutil_py.lo +subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la: $(libsvn_swig_py_DEPS) + cd subversion/bindings/swig/python/libsvn_swig_py && $(LINK) $(libsvn_swig_py_LDFLAGS) -o libsvn_swig_py-1.la $(LT_NO_UNDEFINED) $(libsvn_swig_py_OBJECTS) ../../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_swig_ruby_PATH = subversion/bindings/swig/ruby/libsvn_swig_ruby +libsvn_swig_ruby_DEPS = subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_swig_ruby_OBJECTS = swigutil_rb.lo +subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la: $(libsvn_swig_ruby_DEPS) + cd subversion/bindings/swig/ruby/libsvn_swig_ruby && $(LINK) $(SWIG_RB_LIBS) $(libsvn_swig_ruby_LDFLAGS) -o libsvn_swig_ruby-1.la $(LT_NO_UNDEFINED) $(libsvn_swig_ruby_OBJECTS) ../../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_test_PATH = subversion/tests +libsvn_test_DEPS = subversion/tests/svn_test_fs.lo subversion/tests/svn_test_main.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_test_OBJECTS = svn_test_fs.lo svn_test_main.lo +subversion/tests/libsvn_test-1.la: $(libsvn_test_DEPS) + cd subversion/tests && $(LINK_LIB) $(libsvn_test_LDFLAGS) -o libsvn_test-1.la $(libsvn_test_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvn_wc_PATH = subversion/libsvn_wc +libsvn_wc_DEPS = subversion/libsvn_wc/adm_crawler.lo subversion/libsvn_wc/adm_files.lo subversion/libsvn_wc/adm_ops.lo subversion/libsvn_wc/ambient_depth_filter_editor.lo subversion/libsvn_wc/cleanup.lo subversion/libsvn_wc/conflicts.lo subversion/libsvn_wc/context.lo subversion/libsvn_wc/copy.lo subversion/libsvn_wc/crop.lo subversion/libsvn_wc/delete.lo subversion/libsvn_wc/deprecated.lo subversion/libsvn_wc/diff_editor.lo subversion/libsvn_wc/diff_local.lo subversion/libsvn_wc/entries.lo subversion/libsvn_wc/externals.lo subversion/libsvn_wc/info.lo subversion/libsvn_wc/lock.lo subversion/libsvn_wc/merge.lo subversion/libsvn_wc/node.lo subversion/libsvn_wc/old-and-busted.lo subversion/libsvn_wc/props.lo subversion/libsvn_wc/questions.lo subversion/libsvn_wc/relocate.lo subversion/libsvn_wc/revert.lo subversion/libsvn_wc/revision_status.lo subversion/libsvn_wc/status.lo subversion/libsvn_wc/translate.lo subversion/libsvn_wc/tree_conflicts.lo subversion/libsvn_wc/update_editor.lo subversion/libsvn_wc/upgrade.lo subversion/libsvn_wc/util.lo subversion/libsvn_wc/wc_db.lo subversion/libsvn_wc/wc_db_pristine.lo subversion/libsvn_wc/wc_db_update_move.lo subversion/libsvn_wc/wc_db_util.lo subversion/libsvn_wc/wc_db_wcroot.lo subversion/libsvn_wc/wcroot_anchor.lo subversion/libsvn_wc/workqueue.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +libsvn_wc_OBJECTS = adm_crawler.lo adm_files.lo adm_ops.lo ambient_depth_filter_editor.lo cleanup.lo conflicts.lo context.lo copy.lo crop.lo delete.lo deprecated.lo diff_editor.lo diff_local.lo entries.lo externals.lo info.lo lock.lo merge.lo node.lo old-and-busted.lo props.lo questions.lo relocate.lo revert.lo revision_status.lo status.lo translate.lo tree_conflicts.lo update_editor.lo upgrade.lo util.lo wc_db.lo wc_db_pristine.lo wc_db_update_move.lo wc_db_util.lo wc_db_wcroot.lo wcroot_anchor.lo workqueue.lo +subversion/libsvn_wc/libsvn_wc-1.la: $(libsvn_wc_DEPS) + cd subversion/libsvn_wc && $(LINK_LIB) $(libsvn_wc_LDFLAGS) -o libsvn_wc-1.la $(LT_NO_UNDEFINED) $(libsvn_wc_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvncxxhl_PATH = subversion/bindings/cxxhl +libsvncxxhl_DEPS = subversion/bindings/cxxhl/src/exception.lo subversion/bindings/cxxhl/src/tristate.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs/libsvn_fs-1.la +libsvncxxhl_OBJECTS = src/exception.lo src/tristate.lo +subversion/bindings/cxxhl/libsvncxxhl-1.la: $(libsvncxxhl_DEPS) + cd subversion/bindings/cxxhl && $(LINK_CXX_LIB) $(libsvncxxhl_LDFLAGS) -o libsvncxxhl-1.la $(LT_NO_UNDEFINED) $(libsvncxxhl_OBJECTS) ../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +libsvnjavahl_PATH = subversion/bindings/javahl/native +libsvnjavahl_DEPS = $(javahl_javah_DEPS) $(javahl_java_DEPS) $(javahl_callback_javah_DEPS) $(javahl_types_javah_DEPS) subversion/bindings/javahl/native/Array.lo subversion/bindings/javahl/native/BlameCallback.lo subversion/bindings/javahl/native/ChangelistCallback.lo subversion/bindings/javahl/native/ClientContext.lo subversion/bindings/javahl/native/CommitCallback.lo subversion/bindings/javahl/native/CommitMessage.lo subversion/bindings/javahl/native/CopySources.lo subversion/bindings/javahl/native/CreateJ.lo subversion/bindings/javahl/native/DiffOptions.lo subversion/bindings/javahl/native/DiffSummaryReceiver.lo subversion/bindings/javahl/native/EnumMapper.lo subversion/bindings/javahl/native/File.lo subversion/bindings/javahl/native/ImportFilterCallback.lo subversion/bindings/javahl/native/InfoCallback.lo subversion/bindings/javahl/native/InputStream.lo subversion/bindings/javahl/native/JNIByteArray.lo subversion/bindings/javahl/native/JNICriticalSection.lo subversion/bindings/javahl/native/JNIMutex.lo subversion/bindings/javahl/native/JNIStackElement.lo subversion/bindings/javahl/native/JNIStringHolder.lo subversion/bindings/javahl/native/JNIThreadData.lo subversion/bindings/javahl/native/JNIUtil.lo subversion/bindings/javahl/native/ListCallback.lo subversion/bindings/javahl/native/LogMessageCallback.lo subversion/bindings/javahl/native/MessageReceiver.lo subversion/bindings/javahl/native/OutputStream.lo subversion/bindings/javahl/native/PatchCallback.lo subversion/bindings/javahl/native/Path.lo subversion/bindings/javahl/native/Pool.lo subversion/bindings/javahl/native/Prompter.lo subversion/bindings/javahl/native/ProplistCallback.lo subversion/bindings/javahl/native/ReposFreezeAction.lo subversion/bindings/javahl/native/ReposNotifyCallback.lo subversion/bindings/javahl/native/Revision.lo subversion/bindings/javahl/native/RevisionRange.lo subversion/bindings/javahl/native/RevpropTable.lo subversion/bindings/javahl/native/SVNBase.lo subversion/bindings/javahl/native/SVNClient.lo subversion/bindings/javahl/native/SVNRepos.lo subversion/bindings/javahl/native/StatusCallback.lo subversion/bindings/javahl/native/StringArray.lo subversion/bindings/javahl/native/Targets.lo subversion/bindings/javahl/native/VersionExtended.lo subversion/bindings/javahl/native/libsvnjavahl.la.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_NativeResources.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_types_Version.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_types_VersionExtended.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs/libsvn_fs-1.la +libsvnjavahl_OBJECTS = Array.lo BlameCallback.lo ChangelistCallback.lo ClientContext.lo CommitCallback.lo CommitMessage.lo CopySources.lo CreateJ.lo DiffOptions.lo DiffSummaryReceiver.lo EnumMapper.lo File.lo ImportFilterCallback.lo InfoCallback.lo InputStream.lo JNIByteArray.lo JNICriticalSection.lo JNIMutex.lo JNIStackElement.lo JNIStringHolder.lo JNIThreadData.lo JNIUtil.lo ListCallback.lo LogMessageCallback.lo MessageReceiver.lo OutputStream.lo PatchCallback.lo Path.lo Pool.lo Prompter.lo ProplistCallback.lo ReposFreezeAction.lo ReposNotifyCallback.lo Revision.lo RevisionRange.lo RevpropTable.lo SVNBase.lo SVNClient.lo SVNRepos.lo StatusCallback.lo StringArray.lo Targets.lo VersionExtended.lo libsvnjavahl.la.lo org_apache_subversion_javahl_NativeResources.lo org_apache_subversion_javahl_SVNClient.lo org_apache_subversion_javahl_SVNRepos.lo org_apache_subversion_javahl_types_Version.lo org_apache_subversion_javahl_types_VersionExtended.lo +subversion/bindings/javahl/native/libsvnjavahl-1.la: $(libsvnjavahl_DEPS) + cd subversion/bindings/javahl/native && $(LINK_JAVAHL_CXX) $(libsvnjavahl_LDFLAGS) -o libsvnjavahl-1.la $(LT_NO_UNDEFINED) $(libsvnjavahl_OBJECTS) ../../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la ../../../../subversion/libsvn_fs/libsvn_fs-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +locale_PATH = subversion/po +locale_DEPS = subversion/po/de.mo subversion/po/es.mo subversion/po/fr.mo subversion/po/it.mo subversion/po/ja.mo subversion/po/ko.mo subversion/po/nb.mo subversion/po/pl.mo subversion/po/pt_BR.mo subversion/po/sv.mo subversion/po/zh_CN.mo subversion/po/zh_TW.mo +locale: $(locale_DEPS) + +locks_test_PATH = subversion/tests/libsvn_fs +locks_test_DEPS = subversion/tests/libsvn_fs/locks-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +locks_test_OBJECTS = locks-test.lo +subversion/tests/libsvn_fs/locks-test$(EXEEXT): $(locks_test_DEPS) + cd subversion/tests/libsvn_fs && $(LINK) $(locks_test_LDFLAGS) -o locks-test$(EXEEXT) $(locks_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +mergeinfo_test_PATH = subversion/tests/libsvn_subr +mergeinfo_test_DEPS = subversion/tests/libsvn_subr/mergeinfo-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +mergeinfo_test_OBJECTS = mergeinfo-test.lo +subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT): $(mergeinfo_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(mergeinfo_test_LDFLAGS) -o mergeinfo-test$(EXEEXT) $(mergeinfo_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +mod_authz_svn_PATH = subversion/mod_authz_svn +mod_authz_svn_DEPS = subversion/mod_authz_svn/mod_authz_svn.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/mod_dav_svn/mod_dav_svn.la +mod_authz_svn_OBJECTS = mod_authz_svn.lo +subversion/mod_authz_svn/mod_authz_svn.la: $(mod_authz_svn_DEPS) + if $(INSTALL_APACHE_MODS) ; then cd subversion/mod_authz_svn && $(LINK_APACHE_MOD) $(mod_authz_svn_LDFLAGS) -o mod_authz_svn.la $(LT_NO_UNDEFINED) $(mod_authz_svn_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(LIBS) ; else echo "fake" > subversion/mod_authz_svn/mod_authz_svn.la ; fi + +mod_dav_svn_PATH = subversion/mod_dav_svn +mod_dav_svn_DEPS = subversion/mod_dav_svn/activity.lo subversion/mod_dav_svn/authz.lo subversion/mod_dav_svn/deadprops.lo subversion/mod_dav_svn/liveprops.lo subversion/mod_dav_svn/lock.lo subversion/mod_dav_svn/merge.lo subversion/mod_dav_svn/mirror.lo subversion/mod_dav_svn/mod_dav_svn.lo subversion/mod_dav_svn/posts/create_txn.lo subversion/mod_dav_svn/reports/dated-rev.lo subversion/mod_dav_svn/reports/deleted-rev.lo subversion/mod_dav_svn/reports/file-revs.lo subversion/mod_dav_svn/reports/get-location-segments.lo subversion/mod_dav_svn/reports/get-locations.lo subversion/mod_dav_svn/reports/get-locks.lo subversion/mod_dav_svn/reports/inherited-props.lo subversion/mod_dav_svn/reports/log.lo subversion/mod_dav_svn/reports/mergeinfo.lo subversion/mod_dav_svn/reports/replay.lo subversion/mod_dav_svn/reports/update.lo subversion/mod_dav_svn/repos.lo subversion/mod_dav_svn/util.lo subversion/mod_dav_svn/version.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +mod_dav_svn_OBJECTS = activity.lo authz.lo deadprops.lo liveprops.lo lock.lo merge.lo mirror.lo mod_dav_svn.lo posts/create_txn.lo reports/dated-rev.lo reports/deleted-rev.lo reports/file-revs.lo reports/get-location-segments.lo reports/get-locations.lo reports/get-locks.lo reports/inherited-props.lo reports/log.lo reports/mergeinfo.lo reports/replay.lo reports/update.lo repos.lo util.lo version.lo +subversion/mod_dav_svn/mod_dav_svn.la: $(mod_dav_svn_DEPS) + if $(INSTALL_APACHE_MODS) ; then cd subversion/mod_dav_svn && $(LINK_APACHE_MOD) $(mod_dav_svn_LDFLAGS) -o mod_dav_svn.la $(LT_NO_UNDEFINED) $(mod_dav_svn_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(LIBS) ; else echo "fake" > subversion/mod_dav_svn/mod_dav_svn.la ; fi + +mod_dontdothat_PATH = tools/server-side/mod_dontdothat +mod_dontdothat_DEPS = tools/server-side/mod_dontdothat/mod_dontdothat.lo subversion/libsvn_subr/libsvn_subr-1.la subversion/mod_dav_svn/mod_dav_svn.la +mod_dontdothat_OBJECTS = mod_dontdothat.lo +tools/server-side/mod_dontdothat/mod_dontdothat.la: $(mod_dontdothat_DEPS) + if $(INSTALL_APACHE_MODS) ; then cd tools/server-side/mod_dontdothat && $(LINK_APACHE_MOD) $(mod_dontdothat_LDFLAGS) -o mod_dontdothat.la $(LT_NO_UNDEFINED) $(mod_dontdothat_OBJECTS) ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_XML_LIBS) $(LIBS) ; else echo "fake" > tools/server-side/mod_dontdothat/mod_dontdothat.la ; fi + +named_atomic_proc_test_PATH = subversion/tests/libsvn_subr +named_atomic_proc_test_DEPS = subversion/tests/libsvn_subr/named_atomic-test-proc.lo subversion/libsvn_subr/libsvn_subr-1.la +named_atomic_proc_test_OBJECTS = named_atomic-test-proc.lo +subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT): $(named_atomic_proc_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(named_atomic_proc_test_LDFLAGS) -o named_atomic-proc-test$(EXEEXT) $(named_atomic_proc_test_OBJECTS) ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +named_atomic_test_PATH = subversion/tests/libsvn_subr +named_atomic_test_DEPS = subversion/tests/libsvn_subr/named_atomic-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +named_atomic_test_OBJECTS = named_atomic-test.lo +subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT): $(named_atomic_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(named_atomic_test_LDFLAGS) -o named_atomic-test$(EXEEXT) $(named_atomic_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +op_depth_test_PATH = subversion/tests/libsvn_wc +op_depth_test_DEPS = subversion/tests/libsvn_wc/op-depth-test.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +op_depth_test_OBJECTS = op-depth-test.lo utils.lo +subversion/tests/libsvn_wc/op-depth-test$(EXEEXT): $(op_depth_test_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(op_depth_test_LDFLAGS) -o op-depth-test$(EXEEXT) $(op_depth_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +opt_test_PATH = subversion/tests/libsvn_subr +opt_test_DEPS = subversion/tests/libsvn_subr/opt-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +opt_test_OBJECTS = opt-test.lo +subversion/tests/libsvn_subr/opt-test$(EXEEXT): $(opt_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(opt_test_LDFLAGS) -o opt-test$(EXEEXT) $(opt_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +parse_diff_test_PATH = subversion/tests/libsvn_diff +parse_diff_test_DEPS = subversion/tests/libsvn_diff/parse-diff-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +parse_diff_test_OBJECTS = parse-diff-test.lo +subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT): $(parse_diff_test_DEPS) + cd subversion/tests/libsvn_diff && $(LINK) $(parse_diff_test_LDFLAGS) -o parse-diff-test$(EXEEXT) $(parse_diff_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +path_test_PATH = subversion/tests/libsvn_subr +path_test_DEPS = subversion/tests/libsvn_subr/path-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +path_test_OBJECTS = path-test.lo +subversion/tests/libsvn_subr/path-test$(EXEEXT): $(path_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(path_test_LDFLAGS) -o path-test$(EXEEXT) $(path_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +perl_client_PATH = subversion/bindings/swig/perl/native +perl_client_DEPS = subversion/bindings/swig/perl/native/svn_client.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la +perl_client_OBJECTS = svn_client.lo +subversion/bindings/swig/perl/native/_Client.la: $(perl_client_DEPS) + cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_client_LDFLAGS) -o _Client.la $(LT_NO_UNDEFINED) $(perl_client_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +perl_core_PATH = subversion/bindings/swig/perl/native +perl_core_DEPS = subversion/bindings/swig/perl/native/core.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +perl_core_OBJECTS = core.lo +subversion/bindings/swig/perl/native/_Core.la: $(perl_core_DEPS) + cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_core_LDFLAGS) -o _Core.la $(LT_NO_UNDEFINED) $(perl_core_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +perl_delta_PATH = subversion/bindings/swig/perl/native +perl_delta_DEPS = subversion/bindings/swig/perl/native/svn_delta.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la +perl_delta_OBJECTS = svn_delta.lo +subversion/bindings/swig/perl/native/_Delta.la: $(perl_delta_DEPS) + cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_delta_LDFLAGS) -o _Delta.la $(LT_NO_UNDEFINED) $(perl_delta_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +perl_diff_PATH = subversion/bindings/swig/perl/native +perl_diff_DEPS = subversion/bindings/swig/perl/native/svn_diff.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la +perl_diff_OBJECTS = svn_diff.lo +subversion/bindings/swig/perl/native/_Diff.la: $(perl_diff_DEPS) + cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_diff_LDFLAGS) -o _Diff.la $(LT_NO_UNDEFINED) $(perl_diff_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +perl_fs_PATH = subversion/bindings/swig/perl/native +perl_fs_DEPS = subversion/bindings/swig/perl/native/svn_fs.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la +perl_fs_OBJECTS = svn_fs.lo +subversion/bindings/swig/perl/native/_Fs.la: $(perl_fs_DEPS) + cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_fs_LDFLAGS) -o _Fs.la $(LT_NO_UNDEFINED) $(perl_fs_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +perl_ra_PATH = subversion/bindings/swig/perl/native +perl_ra_DEPS = subversion/bindings/swig/perl/native/svn_ra.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la +perl_ra_OBJECTS = svn_ra.lo +subversion/bindings/swig/perl/native/_Ra.la: $(perl_ra_DEPS) + cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_ra_LDFLAGS) -o _Ra.la $(LT_NO_UNDEFINED) $(perl_ra_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +perl_repos_PATH = subversion/bindings/swig/perl/native +perl_repos_DEPS = subversion/bindings/swig/perl/native/svn_repos.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la +perl_repos_OBJECTS = svn_repos.lo +subversion/bindings/swig/perl/native/_Repos.la: $(perl_repos_DEPS) + cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_repos_LDFLAGS) -o _Repos.la $(LT_NO_UNDEFINED) $(perl_repos_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +perl_wc_PATH = subversion/bindings/swig/perl/native +perl_wc_DEPS = subversion/bindings/swig/perl/native/svn_wc.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la +perl_wc_OBJECTS = svn_wc.lo +subversion/bindings/swig/perl/native/_Wc.la: $(perl_wc_DEPS) + cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_wc_LDFLAGS) -o _Wc.la $(LT_NO_UNDEFINED) $(perl_wc_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +pristine_store_test_PATH = subversion/tests/libsvn_wc +pristine_store_test_DEPS = subversion/tests/libsvn_wc/pristine-store-test.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +pristine_store_test_OBJECTS = pristine-store-test.lo utils.lo +subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT): $(pristine_store_test_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(pristine_store_test_LDFLAGS) -o pristine-store-test$(EXEEXT) $(pristine_store_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +python_client_PATH = subversion/bindings/swig/python +python_client_DEPS = subversion/bindings/swig/python/svn_client.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la +python_client_OBJECTS = svn_client.lo +subversion/bindings/swig/python/_client.la: $(python_client_DEPS) + cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_client_LDFLAGS) -o _client.la $(LT_NO_UNDEFINED) $(python_client_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +python_core_PATH = subversion/bindings/swig/python +python_core_DEPS = subversion/bindings/swig/python/core.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +python_core_OBJECTS = core.lo +subversion/bindings/swig/python/_core.la: $(python_core_DEPS) + cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_core_LDFLAGS) -o _core.la $(LT_NO_UNDEFINED) $(python_core_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +python_delta_PATH = subversion/bindings/swig/python +python_delta_DEPS = subversion/bindings/swig/python/svn_delta.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la +python_delta_OBJECTS = svn_delta.lo +subversion/bindings/swig/python/_delta.la: $(python_delta_DEPS) + cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_delta_LDFLAGS) -o _delta.la $(LT_NO_UNDEFINED) $(python_delta_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +python_diff_PATH = subversion/bindings/swig/python +python_diff_DEPS = subversion/bindings/swig/python/svn_diff.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la +python_diff_OBJECTS = svn_diff.lo +subversion/bindings/swig/python/_diff.la: $(python_diff_DEPS) + cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_diff_LDFLAGS) -o _diff.la $(LT_NO_UNDEFINED) $(python_diff_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +python_fs_PATH = subversion/bindings/swig/python +python_fs_DEPS = subversion/bindings/swig/python/svn_fs.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la +python_fs_OBJECTS = svn_fs.lo +subversion/bindings/swig/python/_fs.la: $(python_fs_DEPS) + cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_fs_LDFLAGS) -o _fs.la $(LT_NO_UNDEFINED) $(python_fs_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +python_ra_PATH = subversion/bindings/swig/python +python_ra_DEPS = subversion/bindings/swig/python/svn_ra.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la +python_ra_OBJECTS = svn_ra.lo +subversion/bindings/swig/python/_ra.la: $(python_ra_DEPS) + cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_ra_LDFLAGS) -o _ra.la $(LT_NO_UNDEFINED) $(python_ra_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +python_repos_PATH = subversion/bindings/swig/python +python_repos_DEPS = subversion/bindings/swig/python/svn_repos.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la +python_repos_OBJECTS = svn_repos.lo +subversion/bindings/swig/python/_repos.la: $(python_repos_DEPS) + cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_repos_LDFLAGS) -o _repos.la $(LT_NO_UNDEFINED) $(python_repos_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +python_wc_PATH = subversion/bindings/swig/python +python_wc_DEPS = subversion/bindings/swig/python/svn_wc.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la +python_wc_OBJECTS = svn_wc.lo +subversion/bindings/swig/python/_wc.la: $(python_wc_DEPS) + cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_wc_LDFLAGS) -o _wc.la $(LT_NO_UNDEFINED) $(python_wc_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ra_local_test_PATH = subversion/tests/libsvn_ra_local +ra_local_test_DEPS = subversion/tests/libsvn_ra_local/ra-local-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_ra_local/libsvn_ra_local-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +ra_local_test_OBJECTS = ra-local-test.lo +subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT): $(ra_local_test_DEPS) + cd subversion/tests/libsvn_ra_local && $(LINK) $(ra_local_test_LDFLAGS) -o ra-local-test$(EXEEXT) $(ra_local_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_ra_local/libsvn_ra_local-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +ra_test_PATH = subversion/tests/libsvn_ra +ra_test_DEPS = subversion/tests/libsvn_ra/ra-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +ra_test_OBJECTS = ra-test.lo +subversion/tests/libsvn_ra/ra-test$(EXEEXT): $(ra_test_DEPS) + cd subversion/tests/libsvn_ra && $(LINK) $(ra_test_LDFLAGS) -o ra-test$(EXEEXT) $(ra_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +random_test_PATH = subversion/tests/libsvn_delta +random_test_DEPS = subversion/tests/libsvn_delta/random-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +random_test_OBJECTS = random-test.lo +subversion/tests/libsvn_delta/random-test$(EXEEXT): $(random_test_DEPS) + cd subversion/tests/libsvn_delta && $(LINK) $(random_test_LDFLAGS) -o random-test$(EXEEXT) $(random_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +repos_test_PATH = subversion/tests/libsvn_repos +repos_test_DEPS = subversion/tests/libsvn_repos/dir-delta-editor.lo subversion/tests/libsvn_repos/repos-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +repos_test_OBJECTS = dir-delta-editor.lo repos-test.lo +subversion/tests/libsvn_repos/repos-test$(EXEEXT): $(repos_test_DEPS) + cd subversion/tests/libsvn_repos && $(LINK) $(repos_test_LDFLAGS) -o repos-test$(EXEEXT) $(repos_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +revision_test_PATH = subversion/tests/libsvn_subr +revision_test_DEPS = subversion/tests/libsvn_subr/revision-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +revision_test_OBJECTS = revision-test.lo +subversion/tests/libsvn_subr/revision-test$(EXEEXT): $(revision_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(revision_test_LDFLAGS) -o revision-test$(EXEEXT) $(revision_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ruby_client_PATH = subversion/bindings/swig/ruby +ruby_client_DEPS = subversion/bindings/swig/ruby/svn_client.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la +ruby_client_OBJECTS = svn_client.lo +subversion/bindings/swig/ruby/client.la: $(ruby_client_DEPS) + cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_client_LDFLAGS) -o client.la $(LT_NO_UNDEFINED) $(ruby_client_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ruby_core_PATH = subversion/bindings/swig/ruby +ruby_core_DEPS = subversion/bindings/swig/ruby/core.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +ruby_core_OBJECTS = core.lo +subversion/bindings/swig/ruby/core.la: $(ruby_core_DEPS) + cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_core_LDFLAGS) -o core.la $(LT_NO_UNDEFINED) $(ruby_core_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ruby_delta_PATH = subversion/bindings/swig/ruby +ruby_delta_DEPS = subversion/bindings/swig/ruby/svn_delta.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la +ruby_delta_OBJECTS = svn_delta.lo +subversion/bindings/swig/ruby/delta.la: $(ruby_delta_DEPS) + cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_delta_LDFLAGS) -o delta.la $(LT_NO_UNDEFINED) $(ruby_delta_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ruby_diff_PATH = subversion/bindings/swig/ruby +ruby_diff_DEPS = subversion/bindings/swig/ruby/svn_diff.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la +ruby_diff_OBJECTS = svn_diff.lo +subversion/bindings/swig/ruby/diff.la: $(ruby_diff_DEPS) + cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_diff_LDFLAGS) -o diff.la $(LT_NO_UNDEFINED) $(ruby_diff_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ruby_fs_PATH = subversion/bindings/swig/ruby +ruby_fs_DEPS = subversion/bindings/swig/ruby/svn_fs.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la +ruby_fs_OBJECTS = svn_fs.lo +subversion/bindings/swig/ruby/fs.la: $(ruby_fs_DEPS) + cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_fs_LDFLAGS) -o fs.la $(LT_NO_UNDEFINED) $(ruby_fs_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ruby_ra_PATH = subversion/bindings/swig/ruby +ruby_ra_DEPS = subversion/bindings/swig/ruby/svn_ra.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la +ruby_ra_OBJECTS = svn_ra.lo +subversion/bindings/swig/ruby/ra.la: $(ruby_ra_DEPS) + cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_ra_LDFLAGS) -o ra.la $(LT_NO_UNDEFINED) $(ruby_ra_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ruby_repos_PATH = subversion/bindings/swig/ruby +ruby_repos_DEPS = subversion/bindings/swig/ruby/svn_repos.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la +ruby_repos_OBJECTS = svn_repos.lo +subversion/bindings/swig/ruby/repos.la: $(ruby_repos_DEPS) + cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_repos_LDFLAGS) -o repos.la $(LT_NO_UNDEFINED) $(ruby_repos_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +ruby_wc_PATH = subversion/bindings/swig/ruby +ruby_wc_DEPS = subversion/bindings/swig/ruby/svn_wc.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la +ruby_wc_OBJECTS = svn_wc.lo +subversion/bindings/swig/ruby/wc.la: $(ruby_wc_DEPS) + cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_wc_LDFLAGS) -o wc.la $(LT_NO_UNDEFINED) $(ruby_wc_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +skel_test_PATH = subversion/tests/libsvn_subr +skel_test_DEPS = subversion/tests/libsvn_subr/skel-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +skel_test_OBJECTS = skel-test.lo +subversion/tests/libsvn_subr/skel-test$(EXEEXT): $(skel_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(skel_test_LDFLAGS) -o skel-test$(EXEEXT) $(skel_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +spillbuf_test_PATH = subversion/tests/libsvn_subr +spillbuf_test_DEPS = subversion/tests/libsvn_subr/spillbuf-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +spillbuf_test_OBJECTS = spillbuf-test.lo +subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT): $(spillbuf_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(spillbuf_test_LDFLAGS) -o spillbuf-test$(EXEEXT) $(spillbuf_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +stream_test_PATH = subversion/tests/libsvn_subr +stream_test_DEPS = subversion/tests/libsvn_subr/stream-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +stream_test_OBJECTS = stream-test.lo +subversion/tests/libsvn_subr/stream-test$(EXEEXT): $(stream_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(stream_test_LDFLAGS) -o stream-test$(EXEEXT) $(stream_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +string_test_PATH = subversion/tests/libsvn_subr +string_test_DEPS = subversion/tests/libsvn_subr/string-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +string_test_OBJECTS = string-test.lo +subversion/tests/libsvn_subr/string-test$(EXEEXT): $(string_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(string_test_LDFLAGS) -o string-test$(EXEEXT) $(string_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +strings_reps_test_PATH = subversion/tests/libsvn_fs_base +strings_reps_test_DEPS = subversion/tests/libsvn_fs_base/strings-reps-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_base/libsvn_fs_base-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +strings_reps_test_OBJECTS = strings-reps-test.lo +subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT): $(strings_reps_test_DEPS) + cd subversion/tests/libsvn_fs_base && $(LINK) $(strings_reps_test_LDFLAGS) -o strings-reps-test$(EXEEXT) $(strings_reps_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_fs_base/libsvn_fs_base-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +subst_translate_test_PATH = subversion/tests/libsvn_subr +subst_translate_test_DEPS = subversion/tests/libsvn_subr/subst_translate-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +subst_translate_test_OBJECTS = subst_translate-test.lo +subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT): $(subst_translate_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(subst_translate_test_LDFLAGS) -o subst_translate-test$(EXEEXT) $(subst_translate_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svn_PATH = subversion/svn +svn_DEPS = subversion/svn/add-cmd.lo subversion/svn/blame-cmd.lo subversion/svn/cat-cmd.lo subversion/svn/changelist-cmd.lo subversion/svn/checkout-cmd.lo subversion/svn/cl-conflicts.lo subversion/svn/cleanup-cmd.lo subversion/svn/commit-cmd.lo subversion/svn/conflict-callbacks.lo subversion/svn/copy-cmd.lo subversion/svn/delete-cmd.lo subversion/svn/deprecated.lo subversion/svn/diff-cmd.lo subversion/svn/export-cmd.lo subversion/svn/file-merge.lo subversion/svn/help-cmd.lo subversion/svn/import-cmd.lo subversion/svn/info-cmd.lo subversion/svn/list-cmd.lo subversion/svn/lock-cmd.lo subversion/svn/log-cmd.lo subversion/svn/merge-cmd.lo subversion/svn/mergeinfo-cmd.lo subversion/svn/mkdir-cmd.lo subversion/svn/move-cmd.lo subversion/svn/notify.lo subversion/svn/patch-cmd.lo subversion/svn/propdel-cmd.lo subversion/svn/propedit-cmd.lo subversion/svn/propget-cmd.lo subversion/svn/proplist-cmd.lo subversion/svn/props.lo subversion/svn/propset-cmd.lo subversion/svn/relocate-cmd.lo subversion/svn/resolve-cmd.lo subversion/svn/resolved-cmd.lo subversion/svn/revert-cmd.lo subversion/svn/status-cmd.lo subversion/svn/status.lo subversion/svn/svn.lo subversion/svn/switch-cmd.lo subversion/svn/unlock-cmd.lo subversion/svn/update-cmd.lo subversion/svn/upgrade-cmd.lo subversion/svn/util.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +svn_OBJECTS = add-cmd.lo blame-cmd.lo cat-cmd.lo changelist-cmd.lo checkout-cmd.lo cl-conflicts.lo cleanup-cmd.lo commit-cmd.lo conflict-callbacks.lo copy-cmd.lo delete-cmd.lo deprecated.lo diff-cmd.lo export-cmd.lo file-merge.lo help-cmd.lo import-cmd.lo info-cmd.lo list-cmd.lo lock-cmd.lo log-cmd.lo merge-cmd.lo mergeinfo-cmd.lo mkdir-cmd.lo move-cmd.lo notify.lo patch-cmd.lo propdel-cmd.lo propedit-cmd.lo propget-cmd.lo proplist-cmd.lo props.lo propset-cmd.lo relocate-cmd.lo resolve-cmd.lo resolved-cmd.lo revert-cmd.lo status-cmd.lo status.lo svn.lo switch-cmd.lo unlock-cmd.lo update-cmd.lo upgrade-cmd.lo util.lo +subversion/svn/svn$(EXEEXT): $(svn_DEPS) + cd subversion/svn && $(LINK) $(svn_LDFLAGS) -o svn$(EXEEXT) $(svn_OBJECTS) ../../subversion/libsvn_client/libsvn_client-1.la ../../subversion/libsvn_wc/libsvn_wc-1.la ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svn_bench_PATH = tools/client-side/svn-bench +svn_bench_DEPS = tools/client-side/svn-bench/help-cmd.lo tools/client-side/svn-bench/notify.lo tools/client-side/svn-bench/null-export-cmd.lo tools/client-side/svn-bench/null-list-cmd.lo tools/client-side/svn-bench/null-log-cmd.lo tools/client-side/svn-bench/svn-bench.lo tools/client-side/svn-bench/util.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_delta/libsvn_delta-1.la +svn_bench_OBJECTS = help-cmd.lo notify.lo null-export-cmd.lo null-list-cmd.lo null-log-cmd.lo svn-bench.lo util.lo +tools/client-side/svn-bench/svn-bench$(EXEEXT): $(svn_bench_DEPS) + cd tools/client-side/svn-bench && $(LINK) $(svn_bench_LDFLAGS) -o svn-bench$(EXEEXT) $(svn_bench_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svn_populate_node_origins_index_PATH = tools/server-side +svn_populate_node_origins_index_DEPS = tools/server-side/svn-populate-node-origins-index.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la +svn_populate_node_origins_index_OBJECTS = svn-populate-node-origins-index.lo +tools/server-side/svn-populate-node-origins-index$(EXEEXT): $(svn_populate_node_origins_index_DEPS) + cd tools/server-side && $(LINK) $(svn_populate_node_origins_index_LDFLAGS) -o svn-populate-node-origins-index$(EXEEXT) $(svn_populate_node_origins_index_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +svn_rep_sharing_stats_PATH = tools/server-side +svn_rep_sharing_stats_DEPS = tools/server-side/svn-rep-sharing-stats.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_fs/libsvn_fs_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la +svn_rep_sharing_stats_OBJECTS = svn-rep-sharing-stats.lo +tools/server-side/svn-rep-sharing-stats$(EXEEXT): $(svn_rep_sharing_stats_DEPS) + cd tools/server-side && $(LINK) $(svn_rep_sharing_stats_LDFLAGS) -o svn-rep-sharing-stats$(EXEEXT) $(svn_rep_sharing_stats_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_fs_fs/libsvn_fs_fs-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svnadmin_PATH = subversion/svnadmin +svnadmin_DEPS = subversion/svnadmin/svnadmin.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +svnadmin_OBJECTS = svnadmin.lo +subversion/svnadmin/svnadmin$(EXEEXT): $(svnadmin_DEPS) + cd subversion/svnadmin && $(LINK) $(svnadmin_LDFLAGS) -o svnadmin$(EXEEXT) $(svnadmin_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svnauthz_PATH = tools/server-side +svnauthz_DEPS = tools/server-side/svnauthz.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la +svnauthz_OBJECTS = svnauthz.lo +tools/server-side/svnauthz$(EXEEXT): $(svnauthz_DEPS) + cd tools/server-side && $(LINK) $(svnauthz_LDFLAGS) -o svnauthz$(EXEEXT) $(svnauthz_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +svnauthz_validate_PATH = tools/server-side +svnauthz_validate_DEPS = tools/server-side/svnauthz.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la +svnauthz_validate_OBJECTS = svnauthz.lo +tools/server-side/svnauthz-validate$(EXEEXT): $(svnauthz_validate_DEPS) + cd tools/server-side && $(LINK) $(svnauthz_validate_LDFLAGS) -o svnauthz-validate$(EXEEXT) $(svnauthz_validate_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +svndiff_test_PATH = subversion/tests/libsvn_delta +svndiff_test_DEPS = subversion/tests/libsvn_delta/svndiff-test.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +svndiff_test_OBJECTS = svndiff-test.lo +subversion/tests/libsvn_delta/svndiff-test$(EXEEXT): $(svndiff_test_DEPS) + cd subversion/tests/libsvn_delta && $(LINK) $(svndiff_test_LDFLAGS) -o svndiff-test$(EXEEXT) $(svndiff_test_OBJECTS) ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svndumpfilter_PATH = subversion/svndumpfilter +svndumpfilter_DEPS = subversion/svndumpfilter/svndumpfilter.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +svndumpfilter_OBJECTS = svndumpfilter.lo +subversion/svndumpfilter/svndumpfilter$(EXEEXT): $(svndumpfilter_DEPS) + cd subversion/svndumpfilter && $(LINK) $(svndumpfilter_LDFLAGS) -o svndumpfilter$(EXEEXT) $(svndumpfilter_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svnlook_PATH = subversion/svnlook +svnlook_DEPS = subversion/svnlook/svnlook.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la +svnlook_OBJECTS = svnlook.lo +subversion/svnlook/svnlook$(EXEEXT): $(svnlook_DEPS) + cd subversion/svnlook && $(LINK) $(svnlook_LDFLAGS) -o svnlook$(EXEEXT) $(svnlook_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svnmucc_PATH = subversion/svnmucc +svnmucc_DEPS = subversion/svnmucc/svnmucc.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_delta/libsvn_delta-1.la +svnmucc_OBJECTS = svnmucc.lo +subversion/svnmucc/svnmucc$(EXEEXT): $(svnmucc_DEPS) + cd subversion/svnmucc && $(LINK) $(svnmucc_LDFLAGS) -o svnmucc$(EXEEXT) $(svnmucc_OBJECTS) ../../subversion/libsvn_client/libsvn_client-1.la ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svnraisetreeconflict_PATH = tools/dev/svnraisetreeconflict +svnraisetreeconflict_DEPS = tools/dev/svnraisetreeconflict/svnraisetreeconflict.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +svnraisetreeconflict_OBJECTS = svnraisetreeconflict.lo +tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT): $(svnraisetreeconflict_DEPS) + cd tools/dev/svnraisetreeconflict && $(LINK) $(svnraisetreeconflict_LDFLAGS) -o svnraisetreeconflict$(EXEEXT) $(svnraisetreeconflict_OBJECTS) ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svnrdump_PATH = subversion/svnrdump +svnrdump_DEPS = subversion/svnrdump/dump_editor.lo subversion/svnrdump/load_editor.lo subversion/svnrdump/svnrdump.lo subversion/svnrdump/util.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +svnrdump_OBJECTS = dump_editor.lo load_editor.lo svnrdump.lo util.lo +subversion/svnrdump/svnrdump$(EXEEXT): $(svnrdump_DEPS) + cd subversion/svnrdump && $(LINK) $(svnrdump_LDFLAGS) -o svnrdump$(EXEEXT) $(svnrdump_OBJECTS) ../../subversion/libsvn_client/libsvn_client-1.la ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +svnserve_PATH = subversion/svnserve +svnserve_DEPS = subversion/svnserve/cyrus_auth.lo subversion/svnserve/log-escape.lo subversion/svnserve/serve.lo subversion/svnserve/svnserve.lo subversion/svnserve/winservice.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_ra_svn/libsvn_ra_svn-1.la +svnserve_OBJECTS = cyrus_auth.lo log-escape.lo serve.lo svnserve.lo winservice.lo +subversion/svnserve/svnserve$(EXEEXT): $(svnserve_DEPS) + cd subversion/svnserve && $(LINK) $(svnserve_LDFLAGS) -o svnserve$(EXEEXT) $(svnserve_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la ../../subversion/libsvn_ra_svn/libsvn_ra_svn-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_SASL_LIBS) $(LIBS) + +svnsync_PATH = subversion/svnsync +svnsync_DEPS = subversion/svnsync/svnsync.lo subversion/svnsync/sync.lo subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +svnsync_OBJECTS = svnsync.lo sync.lo +subversion/svnsync/svnsync$(EXEEXT): $(svnsync_DEPS) + cd subversion/svnsync && $(LINK) $(svnsync_LDFLAGS) -o svnsync$(EXEEXT) $(svnsync_OBJECTS) ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS) + +svnversion_PATH = subversion/svnversion +svnversion_DEPS = subversion/svnversion/svnversion.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +svnversion_OBJECTS = svnversion.lo +subversion/svnversion/svnversion$(EXEEXT): $(svnversion_DEPS) + cd subversion/svnversion && $(LINK) $(svnversion_LDFLAGS) -o svnversion$(EXEEXT) $(svnversion_OBJECTS) ../../subversion/libsvn_wc/libsvn_wc-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +time_test_PATH = subversion/tests/libsvn_subr +time_test_DEPS = subversion/tests/libsvn_subr/time-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +time_test_OBJECTS = time-test.lo +subversion/tests/libsvn_subr/time-test$(EXEEXT): $(time_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(time_test_LDFLAGS) -o time-test$(EXEEXT) $(time_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +translate_test_PATH = subversion/tests/libsvn_subr +translate_test_DEPS = subversion/tests/libsvn_subr/translate-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +translate_test_OBJECTS = translate-test.lo +subversion/tests/libsvn_subr/translate-test$(EXEEXT): $(translate_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(translate_test_LDFLAGS) -o translate-test$(EXEEXT) $(translate_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +utf_test_PATH = subversion/tests/libsvn_subr +utf_test_DEPS = subversion/tests/libsvn_subr/utf-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +utf_test_OBJECTS = utf-test.lo +subversion/tests/libsvn_subr/utf-test$(EXEEXT): $(utf_test_DEPS) + cd subversion/tests/libsvn_subr && $(LINK) $(utf_test_LDFLAGS) -o utf-test$(EXEEXT) $(utf_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +vdelta_test_PATH = subversion/tests/libsvn_delta +vdelta_test_DEPS = subversion/tests/libsvn_delta/vdelta-test.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +vdelta_test_OBJECTS = vdelta-test.lo +subversion/tests/libsvn_delta/vdelta-test$(EXEEXT): $(vdelta_test_DEPS) + cd subversion/tests/libsvn_delta && $(LINK) $(vdelta_test_LDFLAGS) -o vdelta-test$(EXEEXT) $(vdelta_test_OBJECTS) ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +wc_incomplete_tester_PATH = subversion/tests/libsvn_wc +wc_incomplete_tester_DEPS = subversion/tests/libsvn_wc/wc-incomplete-tester.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +wc_incomplete_tester_OBJECTS = wc-incomplete-tester.lo +subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT): $(wc_incomplete_tester_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(wc_incomplete_tester_LDFLAGS) -o wc-incomplete-tester$(EXEEXT) $(wc_incomplete_tester_OBJECTS) ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +wc_lock_tester_PATH = subversion/tests/libsvn_wc +wc_lock_tester_DEPS = subversion/tests/libsvn_wc/wc-lock-tester.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +wc_lock_tester_OBJECTS = wc-lock-tester.lo +subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT): $(wc_lock_tester_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(wc_lock_tester_LDFLAGS) -o wc-lock-tester$(EXEEXT) $(wc_lock_tester_OBJECTS) ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +wc_queries_test_PATH = subversion/tests/libsvn_wc +wc_queries_test_DEPS = subversion/tests/libsvn_wc/wc-queries-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la +wc_queries_test_OBJECTS = wc-queries-test.lo +subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT): $(wc_queries_test_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(wc_queries_test_LDFLAGS) -o wc-queries-test$(EXEEXT) $(wc_queries_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_SQLITE_LIBS) $(LIBS) + +wc_test_PATH = subversion/tests/libsvn_wc +wc_test_DEPS = subversion/tests/libsvn_wc/utils.lo subversion/tests/libsvn_wc/wc-test.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la +wc_test_OBJECTS = utils.lo wc-test.lo +subversion/tests/libsvn_wc/wc-test$(EXEEXT): $(wc_test_DEPS) + cd subversion/tests/libsvn_wc && $(LINK) $(wc_test_LDFLAGS) -o wc-test$(EXEEXT) $(wc_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + +window_test_PATH = subversion/tests/libsvn_delta +window_test_DEPS = subversion/tests/libsvn_delta/window-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la +window_test_OBJECTS = window-test.lo +subversion/tests/libsvn_delta/window-test$(EXEEXT): $(window_test_DEPS) + cd subversion/tests/libsvn_delta && $(LINK) $(window_test_LDFLAGS) -o window-test$(EXEEXT) $(window_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS) + + +######################################## +# Section 6: Install-Group build targets +######################################## + +apache-mod: subversion/mod_authz_svn/mod_authz_svn.la subversion/mod_dav_svn/mod_dav_svn.la + +bdb-lib: subversion/libsvn_fs_base/libsvn_fs_base-1.la + +bdb-test: subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT) + +bin: subversion/svn/svn$(EXEEXT) subversion/svnadmin/svnadmin$(EXEEXT) subversion/svndumpfilter/svndumpfilter$(EXEEXT) subversion/svnlook/svnlook$(EXEEXT) subversion/svnmucc/svnmucc$(EXEEXT) subversion/svnrdump/svnrdump$(EXEEXT) subversion/svnserve/svnserve$(EXEEXT) subversion/svnsync/svnsync$(EXEEXT) subversion/svnversion/svnversion$(EXEEXT) + +cxxhl-lib: subversion/bindings/cxxhl/libsvncxxhl-1.la + +fsmod-lib: subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_fs_fs/libsvn_fs_fs-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la subversion/libsvn_subr/libsvn_subr-1.la + +gnome-keyring-lib: subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring-1.la + +javahl-callback-javah: + +javahl-compat-java: + +javahl-compat-tests: + +javahl-java: + +javahl-javah: + +javahl-lib: subversion/bindings/javahl/native/libsvnjavahl-1.la + +javahl-tests: + +javahl-types-javah: + +kwallet-lib: subversion/libsvn_auth_kwallet/libsvn_auth_kwallet-1.la + +lib: subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_wc/libsvn_wc-1.la + +locale: + +ramod-lib: subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_ra_local/libsvn_ra_local-1.la subversion/libsvn_ra_svn/libsvn_ra_svn-1.la subversion/libsvn_repos/libsvn_repos-1.la + +serf-lib: subversion/libsvn_ra_serf/libsvn_ra_serf-1.la + +sub-test: subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT) + +swig-pl-lib: subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la + +swig-py: subversion/bindings/swig/python/_client.la subversion/bindings/swig/python/_core.la subversion/bindings/swig/python/_delta.la subversion/bindings/swig/python/_diff.la subversion/bindings/swig/python/_fs.la subversion/bindings/swig/python/_ra.la subversion/bindings/swig/python/_repos.la subversion/bindings/swig/python/_wc.la + +swig-py-lib: subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la + +swig-rb: subversion/bindings/swig/ruby/client.la subversion/bindings/swig/ruby/core.la subversion/bindings/swig/ruby/delta.la subversion/bindings/swig/ruby/diff.la subversion/bindings/swig/ruby/fs.la subversion/bindings/swig/ruby/ra.la subversion/bindings/swig/ruby/repos.la subversion/bindings/swig/ruby/wc.la + +swig-rb-lib: subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la + +test: subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) subversion/tests/libsvn_subr/auth-test$(EXEEXT) subversion/tests/libsvn_subr/cache-test$(EXEEXT) subversion/tests/libsvn_subr/checksum-test$(EXEEXT) subversion/tests/libsvn_client/client-test$(EXEEXT) subversion/tests/libsvn_subr/compat-test$(EXEEXT) subversion/tests/libsvn_subr/config-test$(EXEEXT) subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) subversion/tests/libsvn_subr/crypto-test$(EXEEXT) subversion/tests/libsvn_wc/db-test$(EXEEXT) subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) subversion/tests/cmdline/entries-dump$(EXEEXT) subversion/tests/libsvn_subr/error-code-test$(EXEEXT) subversion/tests/libsvn_subr/error-test$(EXEEXT) subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) subversion/tests/libsvn_fs/fs-test$(EXEEXT) subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) subversion/tests/libsvn_subr/io-test$(EXEEXT) subversion/tests/libsvn_test-1.la subversion/tests/libsvn_fs/locks-test$(EXEEXT) subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) subversion/tests/libsvn_subr/opt-test$(EXEEXT) subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) subversion/tests/libsvn_subr/path-test$(EXEEXT) subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) subversion/tests/libsvn_ra/ra-test$(EXEEXT) subversion/tests/libsvn_delta/random-test$(EXEEXT) subversion/tests/libsvn_repos/repos-test$(EXEEXT) subversion/tests/libsvn_subr/revision-test$(EXEEXT) subversion/tests/libsvn_subr/skel-test$(EXEEXT) subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) subversion/tests/libsvn_subr/stream-test$(EXEEXT) subversion/tests/libsvn_subr/string-test$(EXEEXT) subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) subversion/tests/libsvn_subr/time-test$(EXEEXT) subversion/tests/libsvn_subr/translate-test$(EXEEXT) subversion/tests/libsvn_subr/utf-test$(EXEEXT) subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) subversion/tests/libsvn_wc/wc-test$(EXEEXT) subversion/tests/libsvn_delta/window-test$(EXEEXT) + +tests: subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT) + +tools: tools/diff/diff$(EXEEXT) tools/diff/diff3$(EXEEXT) tools/diff/diff4$(EXEEXT) tools/dev/fsfs-access-map$(EXEEXT) tools/dev/fsfs-reorg$(EXEEXT) tools/server-side/fsfs-stats$(EXEEXT) tools/server-side/mod_dontdothat/mod_dontdothat.la tools/client-side/svn-bench/svn-bench$(EXEEXT) tools/server-side/svn-populate-node-origins-index$(EXEEXT) tools/server-side/svn-rep-sharing-stats$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT) + + +######################################## +# Section 7: Install-Group install targets +######################################## + +install-mods-shared: subversion/mod_dav_svn/mod_dav_svn.la subversion/mod_authz_svn/mod_authz_svn.la + if $(INSTALL_APACHE_MODS) ; then cd subversion/mod_dav_svn ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n dav_svn mod_dav_svn.la ; fi + if $(INSTALL_APACHE_MODS) ; then cd subversion/mod_authz_svn ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n authz_svn mod_authz_svn.la ; fi + +install-bdb-lib: subversion/libsvn_fs_base/libsvn_fs_base-1.la + $(MKDIR) $(DESTDIR)$(bdb_libdir) + cd subversion/libsvn_fs_base ; $(INSTALL_BDB_LIB) libsvn_fs_base-1.la $(DESTDIR)$(bdb_libdir)/libsvn_fs_base-1.la + +install-bin: subversion/svn/svn$(EXEEXT) subversion/svnadmin/svnadmin$(EXEEXT) subversion/svndumpfilter/svndumpfilter$(EXEEXT) subversion/svnlook/svnlook$(EXEEXT) subversion/svnmucc/svnmucc$(EXEEXT) subversion/svnrdump/svnrdump$(EXEEXT) subversion/svnserve/svnserve$(EXEEXT) subversion/svnsync/svnsync$(EXEEXT) subversion/svnversion/svnversion$(EXEEXT) + $(MKDIR) $(DESTDIR)$(bindir) + cd subversion/svn ; $(INSTALL_BIN) svn$(EXEEXT) $(DESTDIR)$(bindir)/svn$(EXEEXT) + cd subversion/svnadmin ; $(INSTALL_BIN) svnadmin$(EXEEXT) $(DESTDIR)$(bindir)/svnadmin$(EXEEXT) + cd subversion/svndumpfilter ; $(INSTALL_BIN) svndumpfilter$(EXEEXT) $(DESTDIR)$(bindir)/svndumpfilter$(EXEEXT) + cd subversion/svnlook ; $(INSTALL_BIN) svnlook$(EXEEXT) $(DESTDIR)$(bindir)/svnlook$(EXEEXT) + cd subversion/svnmucc ; $(INSTALL_BIN) svnmucc$(EXEEXT) $(DESTDIR)$(bindir)/svnmucc$(EXEEXT) + cd subversion/svnrdump ; $(INSTALL_BIN) svnrdump$(EXEEXT) $(DESTDIR)$(bindir)/svnrdump$(EXEEXT) + cd subversion/svnserve ; $(INSTALL_BIN) svnserve$(EXEEXT) $(DESTDIR)$(bindir)/svnserve$(EXEEXT) + cd subversion/svnsync ; $(INSTALL_BIN) svnsync$(EXEEXT) $(DESTDIR)$(bindir)/svnsync$(EXEEXT) + cd subversion/svnversion ; $(INSTALL_BIN) svnversion$(EXEEXT) $(DESTDIR)$(bindir)/svnversion$(EXEEXT) + +install-cxxhl-lib: subversion/bindings/cxxhl/libsvncxxhl-1.la + $(MKDIR) $(DESTDIR)$(cxxhl_libdir) + cd subversion/bindings/cxxhl ; $(INSTALL_CXXHL_LIB) libsvncxxhl-1.la $(DESTDIR)$(cxxhl_libdir)/libsvncxxhl-1.la + $(INSTALL_EXTRA_CXXHL_LIB) + +install-fsmod-lib: subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la subversion/libsvn_fs_fs/libsvn_fs_fs-1.la + $(MKDIR) $(DESTDIR)$(fsmod_libdir) + cd subversion/libsvn_subr ; $(INSTALL_FSMOD_LIB) libsvn_subr-1.la $(DESTDIR)$(fsmod_libdir)/libsvn_subr-1.la + cd subversion/libsvn_delta ; $(INSTALL_FSMOD_LIB) libsvn_delta-1.la $(DESTDIR)$(fsmod_libdir)/libsvn_delta-1.la + cd subversion/libsvn_fs_util ; $(INSTALL_FSMOD_LIB) libsvn_fs_util-1.la $(DESTDIR)$(fsmod_libdir)/libsvn_fs_util-1.la + cd subversion/libsvn_fs_fs ; $(INSTALL_FSMOD_LIB) libsvn_fs_fs-1.la $(DESTDIR)$(fsmod_libdir)/libsvn_fs_fs-1.la + +install-gnome-keyring-lib: subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring-1.la + $(MKDIR) $(DESTDIR)$(gnome_keyring_libdir) + cd subversion/libsvn_auth_gnome_keyring ; $(INSTALL_GNOME_KEYRING_LIB) libsvn_auth_gnome_keyring-1.la $(DESTDIR)$(gnome_keyring_libdir)/libsvn_auth_gnome_keyring-1.la + +install-javahl-callback-javah: + $(MKDIR) $(DESTDIR)$(javahl_callback_javahdir) + $(INSTALL_EXTRA_JAVAHL_CALLBACK_JAVAH) + +install-javahl-compat-java: + $(MKDIR) $(DESTDIR)$(javahl_compat_javadir) + $(INSTALL_EXTRA_JAVAHL_COMPAT_JAVA) + +install-javahl-compat-tests: + $(MKDIR) $(DESTDIR)$(javahl_compat_testsdir) + $(INSTALL_EXTRA_JAVAHL_COMPAT_TESTS) + +install-javahl-java: + $(MKDIR) $(DESTDIR)$(javahl_javadir) + $(INSTALL_EXTRA_JAVAHL_JAVA) + +install-javahl-javah: + $(MKDIR) $(DESTDIR)$(javahl_javahdir) + $(INSTALL_EXTRA_JAVAHL_JAVAH) + +install-javahl-lib: subversion/bindings/javahl/native/libsvnjavahl-1.la + $(MKDIR) $(DESTDIR)$(javahl_libdir) + cd subversion/bindings/javahl/native ; $(INSTALL_JAVAHL_LIB) libsvnjavahl-1.la $(DESTDIR)$(javahl_libdir)/libsvnjavahl-1.la + $(INSTALL_EXTRA_JAVAHL_LIB) + +install-javahl-tests: + $(MKDIR) $(DESTDIR)$(javahl_testsdir) + $(INSTALL_EXTRA_JAVAHL_TESTS) + +install-javahl-types-javah: + $(MKDIR) $(DESTDIR)$(javahl_types_javahdir) + $(INSTALL_EXTRA_JAVAHL_TYPES_JAVAH) + +install-kwallet-lib: subversion/libsvn_auth_kwallet/libsvn_auth_kwallet-1.la + $(MKDIR) $(DESTDIR)$(kwallet_libdir) + cd subversion/libsvn_auth_kwallet ; $(INSTALL_KWALLET_LIB) libsvn_auth_kwallet-1.la $(DESTDIR)$(kwallet_libdir)/libsvn_auth_kwallet-1.la + +install-lib: subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_client/libsvn_client-1.la + $(MKDIR) $(DESTDIR)$(libdir) + cd subversion/libsvn_diff ; $(INSTALL_LIB) libsvn_diff-1.la $(DESTDIR)$(libdir)/libsvn_diff-1.la + cd subversion/libsvn_ra ; $(INSTALL_LIB) libsvn_ra-1.la $(DESTDIR)$(libdir)/libsvn_ra-1.la + cd subversion/libsvn_wc ; $(INSTALL_LIB) libsvn_wc-1.la $(DESTDIR)$(libdir)/libsvn_wc-1.la + cd subversion/libsvn_client ; $(INSTALL_LIB) libsvn_client-1.la $(DESTDIR)$(libdir)/libsvn_client-1.la + +install-locale: subversion/po/de.mo subversion/po/es.mo subversion/po/fr.mo subversion/po/it.mo subversion/po/ja.mo subversion/po/ko.mo subversion/po/nb.mo subversion/po/pl.mo subversion/po/pt_BR.mo subversion/po/sv.mo subversion/po/zh_CN.mo subversion/po/zh_TW.mo + $(MKDIR) $(DESTDIR)$(localedir) + $(MKDIR) $(DESTDIR)$(localedir)/de/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) de.mo $(DESTDIR)$(localedir)/de/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/es/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) es.mo $(DESTDIR)$(localedir)/es/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/fr/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) fr.mo $(DESTDIR)$(localedir)/fr/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/it/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) it.mo $(DESTDIR)$(localedir)/it/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/ja/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) ja.mo $(DESTDIR)$(localedir)/ja/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/ko/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) ko.mo $(DESTDIR)$(localedir)/ko/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/nb/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) nb.mo $(DESTDIR)$(localedir)/nb/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/pl/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) pl.mo $(DESTDIR)$(localedir)/pl/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/pt_BR/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) pt_BR.mo $(DESTDIR)$(localedir)/pt_BR/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/sv/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) sv.mo $(DESTDIR)$(localedir)/sv/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/zh_CN/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) zh_CN.mo $(DESTDIR)$(localedir)/zh_CN/LC_MESSAGES/$(PACKAGE_NAME).mo + $(MKDIR) $(DESTDIR)$(localedir)/zh_TW/LC_MESSAGES + cd subversion/po ; $(INSTALL_LOCALE) zh_TW.mo $(DESTDIR)$(localedir)/zh_TW/LC_MESSAGES/$(PACKAGE_NAME).mo + +install-ramod-lib: subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_ra_svn/libsvn_ra_svn-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_ra_local/libsvn_ra_local-1.la + $(MKDIR) $(DESTDIR)$(ramod_libdir) + cd subversion/libsvn_fs ; $(INSTALL_RAMOD_LIB) libsvn_fs-1.la $(DESTDIR)$(ramod_libdir)/libsvn_fs-1.la + cd subversion/libsvn_ra_svn ; $(INSTALL_RAMOD_LIB) libsvn_ra_svn-1.la $(DESTDIR)$(ramod_libdir)/libsvn_ra_svn-1.la + cd subversion/libsvn_repos ; $(INSTALL_RAMOD_LIB) libsvn_repos-1.la $(DESTDIR)$(ramod_libdir)/libsvn_repos-1.la + cd subversion/libsvn_ra_local ; $(INSTALL_RAMOD_LIB) libsvn_ra_local-1.la $(DESTDIR)$(ramod_libdir)/libsvn_ra_local-1.la + +install-serf-lib: subversion/libsvn_ra_serf/libsvn_ra_serf-1.la + $(MKDIR) $(DESTDIR)$(serf_libdir) + cd subversion/libsvn_ra_serf ; $(INSTALL_SERF_LIB) libsvn_ra_serf-1.la $(DESTDIR)$(serf_libdir)/libsvn_ra_serf-1.la + +install-sub-test: subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT) + $(MKDIR) $(DESTDIR)$(sub_testdir) + cd subversion/tests/libsvn_subr ; $(INSTALL_SUB_TEST) named_atomic-proc-test$(EXEEXT) $(DESTDIR)$(sub_testdir)/named_atomic-proc-test$(EXEEXT) + +install-swig-pl-lib: subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la + $(MKDIR) $(DESTDIR)$(swig_pl_libdir) + cd subversion/bindings/swig/perl/libsvn_swig_perl ; $(INSTALL_SWIG_PL_LIB) libsvn_swig_perl-1.la $(DESTDIR)$(swig_pl_libdir)/libsvn_swig_perl-1.la + +install-swig-py: subversion/bindings/swig/python/_core.la subversion/bindings/swig/python/_client.la subversion/bindings/swig/python/_delta.la subversion/bindings/swig/python/_diff.la subversion/bindings/swig/python/_fs.la subversion/bindings/swig/python/_ra.la subversion/bindings/swig/python/_repos.la subversion/bindings/swig/python/_wc.la + $(MKDIR) $(DESTDIR)$(swig_pydir) + cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _core.la $(DESTDIR)$(swig_pydir)/_core.la + cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _client.la $(DESTDIR)$(swig_pydir)/_client.la + cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _delta.la $(DESTDIR)$(swig_pydir)/_delta.la + cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _diff.la $(DESTDIR)$(swig_pydir)/_diff.la + cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _fs.la $(DESTDIR)$(swig_pydir)/_fs.la + cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _ra.la $(DESTDIR)$(swig_pydir)/_ra.la + cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _repos.la $(DESTDIR)$(swig_pydir)/_repos.la + cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _wc.la $(DESTDIR)$(swig_pydir)/_wc.la + $(INSTALL_EXTRA_SWIG_PY) + +install-swig-py-lib: subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la + $(MKDIR) $(DESTDIR)$(swig_py_libdir) + cd subversion/bindings/swig/python/libsvn_swig_py ; $(INSTALL_SWIG_PY_LIB) libsvn_swig_py-1.la $(DESTDIR)$(swig_py_libdir)/libsvn_swig_py-1.la + +install-swig-rb: subversion/bindings/swig/ruby/core.la subversion/bindings/swig/ruby/client.la subversion/bindings/swig/ruby/delta.la subversion/bindings/swig/ruby/diff.la subversion/bindings/swig/ruby/fs.la subversion/bindings/swig/ruby/ra.la subversion/bindings/swig/ruby/repos.la subversion/bindings/swig/ruby/wc.la + $(MKDIR) $(DESTDIR)$(swig_rbdir) + cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) core.la $(DESTDIR)$(swig_rbdir)/core.la + cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) client.la $(DESTDIR)$(swig_rbdir)/client.la + cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) delta.la $(DESTDIR)$(swig_rbdir)/delta.la + cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) diff.la $(DESTDIR)$(swig_rbdir)/diff.la + cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) fs.la $(DESTDIR)$(swig_rbdir)/fs.la + cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) ra.la $(DESTDIR)$(swig_rbdir)/ra.la + cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) repos.la $(DESTDIR)$(swig_rbdir)/repos.la + cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) wc.la $(DESTDIR)$(swig_rbdir)/wc.la + $(INSTALL_EXTRA_SWIG_RB) + +install-swig-rb-lib: subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la + $(MKDIR) $(DESTDIR)$(swig_rb_libdir) + cd subversion/bindings/swig/ruby/libsvn_swig_ruby ; $(INSTALL_SWIG_RB_LIB) libsvn_swig_ruby-1.la $(DESTDIR)$(swig_rb_libdir)/libsvn_swig_ruby-1.la + +install-tests: subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT) + $(MKDIR) $(DESTDIR)$(testsdir) + cd subversion/bindings/cxxhl ; $(INSTALL_TESTS) cxxhl-tests$(EXEEXT) $(DESTDIR)$(testsdir)/cxxhl-tests$(EXEEXT) + +install-tools: tools/diff/diff$(EXEEXT) tools/diff/diff3$(EXEEXT) tools/diff/diff4$(EXEEXT) tools/dev/fsfs-access-map$(EXEEXT) tools/dev/fsfs-reorg$(EXEEXT) tools/server-side/fsfs-stats$(EXEEXT) tools/server-side/mod_dontdothat/mod_dontdothat.la tools/client-side/svn-bench/svn-bench$(EXEEXT) tools/server-side/svn-populate-node-origins-index$(EXEEXT) tools/server-side/svn-rep-sharing-stats$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT) + $(MKDIR) $(DESTDIR)$(toolsdir) + cd tools/diff ; $(INSTALL_TOOLS) diff$(EXEEXT) $(DESTDIR)$(toolsdir)/diff$(EXEEXT) + cd tools/diff ; $(INSTALL_TOOLS) diff3$(EXEEXT) $(DESTDIR)$(toolsdir)/diff3$(EXEEXT) + cd tools/diff ; $(INSTALL_TOOLS) diff4$(EXEEXT) $(DESTDIR)$(toolsdir)/diff4$(EXEEXT) + cd tools/dev ; $(INSTALL_TOOLS) fsfs-access-map$(EXEEXT) $(DESTDIR)$(toolsdir)/fsfs-access-map$(EXEEXT) + cd tools/dev ; $(INSTALL_TOOLS) fsfs-reorg$(EXEEXT) $(DESTDIR)$(toolsdir)/fsfs-reorg$(EXEEXT) + cd tools/server-side ; $(INSTALL_TOOLS) fsfs-stats$(EXEEXT) $(DESTDIR)$(toolsdir)/fsfs-stats$(EXEEXT) + if $(INSTALL_APACHE_MODS) ; then cd tools/server-side/mod_dontdothat ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n dontdothat mod_dontdothat.la ; fi + cd tools/client-side/svn-bench ; $(INSTALL_TOOLS) svn-bench$(EXEEXT) $(DESTDIR)$(toolsdir)/svn-bench$(EXEEXT) + cd tools/server-side ; $(INSTALL_TOOLS) svn-populate-node-origins-index$(EXEEXT) $(DESTDIR)$(toolsdir)/svn-populate-node-origins-index$(EXEEXT) + cd tools/server-side ; $(INSTALL_TOOLS) svn-rep-sharing-stats$(EXEEXT) $(DESTDIR)$(toolsdir)/svn-rep-sharing-stats$(EXEEXT) + cd tools/server-side ; $(INSTALL_TOOLS) svnauthz$(EXEEXT) $(DESTDIR)$(toolsdir)/svnauthz$(EXEEXT) + cd tools/server-side ; $(INSTALL_TOOLS) svnauthz-validate$(EXEEXT) $(DESTDIR)$(toolsdir)/svnauthz-validate$(EXEEXT) + cd tools/dev/svnraisetreeconflict ; $(INSTALL_TOOLS) svnraisetreeconflict$(EXEEXT) $(DESTDIR)$(toolsdir)/svnraisetreeconflict$(EXEEXT) + $(INSTALL_EXTRA_TOOLS) + + +######################################## +# Section 8: The install-include rule +######################################## + +install-include: subversion/include/mod_authz_svn.h subversion/include/mod_dav_svn.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_md5.h subversion/include/svn_mergeinfo.h subversion/include/svn_nls.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_quoprint.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/include/svn_xml.h + $(MKDIR) $(DESTDIR)$(includedir)/subversion-1 + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/mod_authz_svn.h $(DESTDIR)$(includedir)/subversion-1/mod_authz_svn.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/mod_dav_svn.h $(DESTDIR)$(includedir)/subversion-1/mod_dav_svn.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_auth.h $(DESTDIR)$(includedir)/subversion-1/svn_auth.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_base64.h $(DESTDIR)$(includedir)/subversion-1/svn_base64.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_cache_config.h $(DESTDIR)$(includedir)/subversion-1/svn_cache_config.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_checksum.h $(DESTDIR)$(includedir)/subversion-1/svn_checksum.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_client.h $(DESTDIR)$(includedir)/subversion-1/svn_client.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_cmdline.h $(DESTDIR)$(includedir)/subversion-1/svn_cmdline.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_compat.h $(DESTDIR)$(includedir)/subversion-1/svn_compat.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_config.h $(DESTDIR)$(includedir)/subversion-1/svn_config.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_ctype.h $(DESTDIR)$(includedir)/subversion-1/svn_ctype.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_dav.h $(DESTDIR)$(includedir)/subversion-1/svn_dav.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_delta.h $(DESTDIR)$(includedir)/subversion-1/svn_delta.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_diff.h $(DESTDIR)$(includedir)/subversion-1/svn_diff.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_dirent_uri.h $(DESTDIR)$(includedir)/subversion-1/svn_dirent_uri.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_dso.h $(DESTDIR)$(includedir)/subversion-1/svn_dso.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_error.h $(DESTDIR)$(includedir)/subversion-1/svn_error.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_error_codes.h $(DESTDIR)$(includedir)/subversion-1/svn_error_codes.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_fs.h $(DESTDIR)$(includedir)/subversion-1/svn_fs.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_hash.h $(DESTDIR)$(includedir)/subversion-1/svn_hash.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_io.h $(DESTDIR)$(includedir)/subversion-1/svn_io.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_iter.h $(DESTDIR)$(includedir)/subversion-1/svn_iter.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_md5.h $(DESTDIR)$(includedir)/subversion-1/svn_md5.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_mergeinfo.h $(DESTDIR)$(includedir)/subversion-1/svn_mergeinfo.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_nls.h $(DESTDIR)$(includedir)/subversion-1/svn_nls.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_opt.h $(DESTDIR)$(includedir)/subversion-1/svn_opt.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_path.h $(DESTDIR)$(includedir)/subversion-1/svn_path.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_pools.h $(DESTDIR)$(includedir)/subversion-1/svn_pools.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_props.h $(DESTDIR)$(includedir)/subversion-1/svn_props.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_quoprint.h $(DESTDIR)$(includedir)/subversion-1/svn_quoprint.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_ra.h $(DESTDIR)$(includedir)/subversion-1/svn_ra.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_ra_svn.h $(DESTDIR)$(includedir)/subversion-1/svn_ra_svn.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_repos.h $(DESTDIR)$(includedir)/subversion-1/svn_repos.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_sorts.h $(DESTDIR)$(includedir)/subversion-1/svn_sorts.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_string.h $(DESTDIR)$(includedir)/subversion-1/svn_string.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_subst.h $(DESTDIR)$(includedir)/subversion-1/svn_subst.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_time.h $(DESTDIR)$(includedir)/subversion-1/svn_time.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_types.h $(DESTDIR)$(includedir)/subversion-1/svn_types.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_user.h $(DESTDIR)$(includedir)/subversion-1/svn_user.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_utf.h $(DESTDIR)$(includedir)/subversion-1/svn_utf.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_version.h $(DESTDIR)$(includedir)/subversion-1/svn_version.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_wc.h $(DESTDIR)$(includedir)/subversion-1/svn_wc.h + $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_xml.h $(DESTDIR)$(includedir)/subversion-1/svn_xml.h + +######################################## +# Section 9: Shortcut targets for manual builds of specific items +######################################## + +atomic-ra-revprop-change: subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) +auth-test: subversion/tests/libsvn_subr/auth-test$(EXEEXT) +cache-test: subversion/tests/libsvn_subr/cache-test$(EXEEXT) +changes-test: subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) +checksum-test: subversion/tests/libsvn_subr/checksum-test$(EXEEXT) +client-test: subversion/tests/libsvn_client/client-test$(EXEEXT) +compat-test: subversion/tests/libsvn_subr/compat-test$(EXEEXT) +config-test: subversion/tests/libsvn_subr/config-test$(EXEEXT) +conflict-data-test: subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) +crypto-test: subversion/tests/libsvn_subr/crypto-test$(EXEEXT) +cxxhl-tests: subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT) +db-test: subversion/tests/libsvn_wc/db-test$(EXEEXT) +diff: tools/diff/diff$(EXEEXT) +diff-diff3-test: subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) +diff3: tools/diff/diff3$(EXEEXT) +diff4: tools/diff/diff4$(EXEEXT) +dirent_uri-test: subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) +entries-compat-test: subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) +entries-dump: subversion/tests/cmdline/entries-dump$(EXEEXT) +error-code-test: subversion/tests/libsvn_subr/error-code-test$(EXEEXT) +error-test: subversion/tests/libsvn_subr/error-test$(EXEEXT) +fs-base-test: subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) +fs-pack-test: subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) +fs-test: subversion/tests/libsvn_fs/fs-test$(EXEEXT) +fsfs-access-map: tools/dev/fsfs-access-map$(EXEEXT) +fsfs-reorg: tools/dev/fsfs-reorg$(EXEEXT) +fsfs-stats: tools/server-side/fsfs-stats$(EXEEXT) +hashdump-test: subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) +io-test: subversion/tests/libsvn_subr/io-test$(EXEEXT) +libsvn_auth_gnome_keyring: subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring-1.la +libsvn_auth_kwallet: subversion/libsvn_auth_kwallet/libsvn_auth_kwallet-1.la +libsvn_client: subversion/libsvn_client/libsvn_client-1.la +libsvn_delta: subversion/libsvn_delta/libsvn_delta-1.la +libsvn_diff: subversion/libsvn_diff/libsvn_diff-1.la +libsvn_fs: subversion/libsvn_fs/libsvn_fs-1.la +libsvn_fs_base: subversion/libsvn_fs_base/libsvn_fs_base-1.la +libsvn_fs_fs: subversion/libsvn_fs_fs/libsvn_fs_fs-1.la +libsvn_fs_util: subversion/libsvn_fs_util/libsvn_fs_util-1.la +libsvn_ra: subversion/libsvn_ra/libsvn_ra-1.la +libsvn_ra_local: subversion/libsvn_ra_local/libsvn_ra_local-1.la +libsvn_ra_serf: subversion/libsvn_ra_serf/libsvn_ra_serf-1.la +libsvn_ra_svn: subversion/libsvn_ra_svn/libsvn_ra_svn-1.la +libsvn_repos: subversion/libsvn_repos/libsvn_repos-1.la +libsvn_subr: subversion/libsvn_subr/libsvn_subr-1.la +libsvn_swig_perl: subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la +libsvn_swig_py: subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la +libsvn_swig_ruby: subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la +libsvn_test: subversion/tests/libsvn_test-1.la +libsvn_wc: subversion/libsvn_wc/libsvn_wc-1.la +libsvncxxhl: subversion/bindings/cxxhl/libsvncxxhl-1.la +libsvnjavahl: subversion/bindings/javahl/native/libsvnjavahl-1.la +locks-test: subversion/tests/libsvn_fs/locks-test$(EXEEXT) +mergeinfo-test: subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) +mod_authz_svn: subversion/mod_authz_svn/mod_authz_svn.la +mod_dav_svn: subversion/mod_dav_svn/mod_dav_svn.la +mod_dontdothat: tools/server-side/mod_dontdothat/mod_dontdothat.la +named_atomic-proc-test: subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT) +named_atomic-test: subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) +op-depth-test: subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) +opt-test: subversion/tests/libsvn_subr/opt-test$(EXEEXT) +parse-diff-test: subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) +path-test: subversion/tests/libsvn_subr/path-test$(EXEEXT) +perl_client: subversion/bindings/swig/perl/native/_Client.la +perl_core: subversion/bindings/swig/perl/native/_Core.la +perl_delta: subversion/bindings/swig/perl/native/_Delta.la +perl_diff: subversion/bindings/swig/perl/native/_Diff.la +perl_fs: subversion/bindings/swig/perl/native/_Fs.la +perl_ra: subversion/bindings/swig/perl/native/_Ra.la +perl_repos: subversion/bindings/swig/perl/native/_Repos.la +perl_wc: subversion/bindings/swig/perl/native/_Wc.la +pristine-store-test: subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) +python_client: subversion/bindings/swig/python/_client.la +python_core: subversion/bindings/swig/python/_core.la +python_delta: subversion/bindings/swig/python/_delta.la +python_diff: subversion/bindings/swig/python/_diff.la +python_fs: subversion/bindings/swig/python/_fs.la +python_ra: subversion/bindings/swig/python/_ra.la +python_repos: subversion/bindings/swig/python/_repos.la +python_wc: subversion/bindings/swig/python/_wc.la +ra-local-test: subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) +ra-test: subversion/tests/libsvn_ra/ra-test$(EXEEXT) +random-test: subversion/tests/libsvn_delta/random-test$(EXEEXT) +repos-test: subversion/tests/libsvn_repos/repos-test$(EXEEXT) +revision-test: subversion/tests/libsvn_subr/revision-test$(EXEEXT) +ruby_client: subversion/bindings/swig/ruby/client.la +ruby_core: subversion/bindings/swig/ruby/core.la +ruby_delta: subversion/bindings/swig/ruby/delta.la +ruby_diff: subversion/bindings/swig/ruby/diff.la +ruby_fs: subversion/bindings/swig/ruby/fs.la +ruby_ra: subversion/bindings/swig/ruby/ra.la +ruby_repos: subversion/bindings/swig/ruby/repos.la +ruby_wc: subversion/bindings/swig/ruby/wc.la +skel-test: subversion/tests/libsvn_subr/skel-test$(EXEEXT) +spillbuf-test: subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) +stream-test: subversion/tests/libsvn_subr/stream-test$(EXEEXT) +string-test: subversion/tests/libsvn_subr/string-test$(EXEEXT) +strings-reps-test: subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT) +subst_translate-test: subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) +svn: subversion/svn/svn$(EXEEXT) +svn-bench: tools/client-side/svn-bench/svn-bench$(EXEEXT) +svn-populate-node-origins-index: tools/server-side/svn-populate-node-origins-index$(EXEEXT) +svn-rep-sharing-stats: tools/server-side/svn-rep-sharing-stats$(EXEEXT) +svnadmin: subversion/svnadmin/svnadmin$(EXEEXT) +svnauthz: tools/server-side/svnauthz$(EXEEXT) +svnauthz-validate: tools/server-side/svnauthz-validate$(EXEEXT) +svndiff-test: subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) +svndumpfilter: subversion/svndumpfilter/svndumpfilter$(EXEEXT) +svnlook: subversion/svnlook/svnlook$(EXEEXT) +svnmucc: subversion/svnmucc/svnmucc$(EXEEXT) +svnraisetreeconflict: tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT) +svnrdump: subversion/svnrdump/svnrdump$(EXEEXT) +svnserve: subversion/svnserve/svnserve$(EXEEXT) +svnsync: subversion/svnsync/svnsync$(EXEEXT) +svnversion: subversion/svnversion/svnversion$(EXEEXT) +time-test: subversion/tests/libsvn_subr/time-test$(EXEEXT) +translate-test: subversion/tests/libsvn_subr/translate-test$(EXEEXT) +utf-test: subversion/tests/libsvn_subr/utf-test$(EXEEXT) +vdelta-test: subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) +wc-incomplete-tester: subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) +wc-lock-tester: subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT) +wc-queries-test: subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) +wc-test: subversion/tests/libsvn_wc/wc-test$(EXEEXT) +window-test: subversion/tests/libsvn_delta/window-test$(EXEEXT) + +######################################## +# Section 10: Rules to build all other kinds of object-like files +######################################## + +subversion/bindings/cxxhl/src/exception.lo: subversion/bindings/cxxhl/src/exception.cpp subversion/bindings/cxxhl/include/svncxxhl/_compat.hpp subversion/bindings/cxxhl/include/svncxxhl/exception.hpp subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_error_private.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h + $(COMPILE_CXXHL_CXX) $(canonicalized_srcdir)subversion/bindings/cxxhl/src/exception.cpp + +subversion/bindings/cxxhl/src/tristate.lo: subversion/bindings/cxxhl/src/tristate.cpp subversion/bindings/cxxhl/include/svncxxhl/tristate.hpp subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h + $(COMPILE_CXXHL_CXX) $(canonicalized_srcdir)subversion/bindings/cxxhl/src/tristate.cpp + +subversion/bindings/cxxhl/tests/test_exception.lo: subversion/bindings/cxxhl/tests/test_exception.cpp subversion/bindings/cxxhl/include/svncxxhl.hpp subversion/bindings/cxxhl/include/svncxxhl/_compat.hpp subversion/bindings/cxxhl/include/svncxxhl/exception.hpp subversion/bindings/cxxhl/include/svncxxhl/tristate.hpp subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h + $(COMPILE_CXXHL_CXX) $(canonicalized_srcdir)subversion/bindings/cxxhl/tests/test_exception.cpp + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/BasicTests.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientException.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientException.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientNotifyInformation.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitInfo.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitInfo.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItem.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitItem.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItemStateFlags.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitItemStateFlags.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictDescriptor.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictDescriptor.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictResult.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictResult.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/DiffSummary.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/DiffSummary.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNClient.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNClient.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNRepos.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNRepos.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/JNIError.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/JNIError.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeException.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeException.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeResources.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeResources.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/ProgressEvent.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ProgressEvent.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/ReposNotifyInformation.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ReposNotifyInformation.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/RunTests.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/RunTests.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNClient.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNClient.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNRepos.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNRepos.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNReposTests.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/SVNReposTests.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNTests.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/SVNTests.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/SubversionException.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/SubversionException.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/WC.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/WC.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/BlameCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ChangelistCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ChangelistCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ClientNotifyCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ClientNotifyCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/CommitCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitMessageCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/CommitMessageCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ConflictResolverCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ConflictResolverCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/DiffSummaryCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/DiffSummaryCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ImportFilterCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ImportFilterCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InfoCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/InfoCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InheritedProplistCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/InheritedProplistCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ListCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ListCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/LogMessageCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/PatchCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/PatchCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProgressCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProgressCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProplistCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposFreezeAction.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ReposFreezeAction.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposNotifyCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ReposNotifyCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/StatusCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/StatusCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/UserPasswordCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/UserPasswordCallback.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ChangePath.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/ChangePath.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Checksum.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Checksum.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ConflictVersion.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/ConflictVersion.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/CopySource.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/CopySource.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Depth.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Depth.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DiffOptions.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/DiffOptions.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DirEntry.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/DirEntry.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Info.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Info.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Lock.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Lock.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/LogDate.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/LogDate.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Mergeinfo.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Mergeinfo.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/NodeKind.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/NodeKind.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Property.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Property.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Revision.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Revision.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/RevisionRange.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/RevisionRange.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Status.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Status.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Tristate.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Tristate.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Version.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Version.java + +subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/VersionExtended.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/VersionExtended.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BasicTests.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/BasicTests.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback2.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback2.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback3.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback3.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallbackImpl.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallbackImpl.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ChangePath.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ChangePath.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ChangelistCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ChangelistCallback.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ClientException.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ClientException.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitItem.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitItem.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitItemStateFlags.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitItemStateFlags.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitMessage.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitMessage.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictDescriptor.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictDescriptor.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictResolverCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictResolverCallback.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictResult.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictResult.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictVersion.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictVersion.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CopySource.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/CopySource.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Depth.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Depth.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DiffSummary.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/DiffSummary.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DiffSummaryReceiver.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/DiffSummaryReceiver.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DirEntry.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/DirEntry.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ErrorCodes.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ErrorCodes.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Info.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Info.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Info2.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Info2.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/InfoCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/InfoCallback.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/InputInterface.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/InputInterface.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ListCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ListCallback.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Lock.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Lock.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LockStatus.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/LockStatus.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogDate.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogDate.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogMessage.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogMessage.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogMessageCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogMessageCallback.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Mergeinfo.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Mergeinfo.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/MergeinfoLogKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/MergeinfoLogKind.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NativeException.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NativeException.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NodeKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NodeKind.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Notify.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Notify.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Notify2.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Notify2.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyAction.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyAction.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyInformation.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyInformation.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyStatus.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyStatus.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Operation.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Operation.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/OutputInterface.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/OutputInterface.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Path.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Path.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProgressEvent.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProgressEvent.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProgressListener.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProgressListener.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword2.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword2.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword3.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword3.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PropertyData.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/PropertyData.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProplistCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallback.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProplistCallbackImpl.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Revision.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Revision.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RevisionKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/RevisionKind.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RevisionRange.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/RevisionRange.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RunTests.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/RunTests.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNAdmin.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNAdmin.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNAdminTests.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/SVNAdminTests.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClient.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientInterface.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientLogLevel.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientLogLevel.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientSynchronized.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNInputStream.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNInputStream.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNOutputStream.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNOutputStream.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNTests.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/SVNTests.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ScheduleKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ScheduleKind.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Status.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Status.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/StatusCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/StatusCallback.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/StatusKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/StatusKind.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SubversionException.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SubversionException.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Version.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Version.java + +subversion/bindings/javahl/classes/org/tigris/subversion/javahl/WC.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/WC.java + +subversion/bindings/javahl/include/BlameCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/BlameCallback.class + +subversion/bindings/javahl/include/ChangePath.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ChangePath.class + +subversion/bindings/javahl/include/ChangelistCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ChangelistCallback.class + +subversion/bindings/javahl/include/Checksum.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Checksum.class + +subversion/bindings/javahl/include/ClientException.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientException.class + +subversion/bindings/javahl/include/ClientNotifyCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ClientNotifyCallback.class + +subversion/bindings/javahl/include/ClientNotifyInformation.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientNotifyInformation.class + +subversion/bindings/javahl/include/CommitCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitCallback.class + +subversion/bindings/javahl/include/CommitInfo.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitInfo.class + +subversion/bindings/javahl/include/CommitItem.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItem.class + +subversion/bindings/javahl/include/CommitItemStateFlags.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItemStateFlags.class + +subversion/bindings/javahl/include/CommitMessageCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitMessageCallback.class + +subversion/bindings/javahl/include/ConflictDescriptor.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictDescriptor.class + +subversion/bindings/javahl/include/ConflictResolverCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ConflictResolverCallback.class + +subversion/bindings/javahl/include/ConflictResult.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictResult.class + +subversion/bindings/javahl/include/ConflictVersion.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ConflictVersion.class + +subversion/bindings/javahl/include/CopySource.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/CopySource.class + +subversion/bindings/javahl/include/Depth.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Depth.class + +subversion/bindings/javahl/include/DiffOptions.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DiffOptions.class + +subversion/bindings/javahl/include/DiffSummary.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/DiffSummary.class + +subversion/bindings/javahl/include/DiffSummaryCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/DiffSummaryCallback.class + +subversion/bindings/javahl/include/DirEntry.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DirEntry.class + +subversion/bindings/javahl/include/ISVNClient.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNClient.class + +subversion/bindings/javahl/include/ISVNRepos.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNRepos.class + +subversion/bindings/javahl/include/ImportFilterCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ImportFilterCallback.class + +subversion/bindings/javahl/include/Info.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Info.class + +subversion/bindings/javahl/include/InfoCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InfoCallback.class + +subversion/bindings/javahl/include/InheritedProplistCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InheritedProplistCallback.class + +subversion/bindings/javahl/include/JNIError.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/JNIError.class + +subversion/bindings/javahl/include/ListCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ListCallback.class + +subversion/bindings/javahl/include/Lock.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Lock.class + +subversion/bindings/javahl/include/LogDate.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/LogDate.class + +subversion/bindings/javahl/include/LogMessageCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/LogMessageCallback.class + +subversion/bindings/javahl/include/Mergeinfo.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Mergeinfo.class + +subversion/bindings/javahl/include/NativeException.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeException.class + +subversion/bindings/javahl/include/NativeResources.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeResources.class + +subversion/bindings/javahl/include/NodeKind.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/NodeKind.class + +subversion/bindings/javahl/include/PatchCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/PatchCallback.class + +subversion/bindings/javahl/include/ProgressCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProgressCallback.class + +subversion/bindings/javahl/include/ProgressEvent.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ProgressEvent.class + +subversion/bindings/javahl/include/Property.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Property.class + +subversion/bindings/javahl/include/ProplistCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProplistCallback.class + +subversion/bindings/javahl/include/ReposFreezeAction.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposFreezeAction.class + +subversion/bindings/javahl/include/ReposNotifyCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposNotifyCallback.class + +subversion/bindings/javahl/include/ReposNotifyInformation.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ReposNotifyInformation.class + +subversion/bindings/javahl/include/Revision.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Revision.class + +subversion/bindings/javahl/include/RevisionRange.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/RevisionRange.class + +subversion/bindings/javahl/include/SVNClient.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNClient.class + +subversion/bindings/javahl/include/SVNRepos.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNRepos.class + +subversion/bindings/javahl/include/Status.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Status.class + +subversion/bindings/javahl/include/StatusCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/StatusCallback.class + +subversion/bindings/javahl/include/SubversionException.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/SubversionException.class + +subversion/bindings/javahl/include/Tristate.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Tristate.class + +subversion/bindings/javahl/include/UserPasswordCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/UserPasswordCallback.class + +subversion/bindings/javahl/include/Version.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Version.class + +subversion/bindings/javahl/include/VersionExtended.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/VersionExtended.class + +subversion/bindings/javahl/native/Array.lo: subversion/bindings/javahl/native/Array.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Array.cpp + +subversion/bindings/javahl/native/BlameCallback.lo: subversion/bindings/javahl/native/BlameCallback.cpp subversion/bindings/javahl/native/BlameCallback.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/BlameCallback.cpp + +subversion/bindings/javahl/native/ChangelistCallback.lo: subversion/bindings/javahl/native/ChangelistCallback.cpp subversion/bindings/javahl/native/ChangelistCallback.h subversion/bindings/javahl/native/ClientContext.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNClient.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ChangelistCallback.cpp + +subversion/bindings/javahl/native/ClientContext.lo: subversion/bindings/javahl/native/ClientContext.cpp subversion/bindings/javahl/native/ClientContext.h subversion/bindings/javahl/native/CommitMessage.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNICriticalSection.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Prompter.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ClientContext.cpp + +subversion/bindings/javahl/native/CommitCallback.lo: subversion/bindings/javahl/native/CommitCallback.cpp subversion/bindings/javahl/native/CommitCallback.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/CommitCallback.cpp + +subversion/bindings/javahl/native/CommitMessage.lo: subversion/bindings/javahl/native/CommitMessage.cpp subversion/bindings/javahl/native/CommitMessage.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/CommitMessage.cpp + +subversion/bindings/javahl/native/CopySources.lo: subversion/bindings/javahl/native/CopySources.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/CopySources.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Revision.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/CopySources.cpp + +subversion/bindings/javahl/native/CreateJ.lo: subversion/bindings/javahl/native/CreateJ.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItemStateFlags.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Revision.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/RevisionRange.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/CreateJ.cpp + +subversion/bindings/javahl/native/DiffOptions.lo: subversion/bindings/javahl/native/DiffOptions.cpp subversion/bindings/javahl/native/DiffOptions.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/DiffOptions.cpp + +subversion/bindings/javahl/native/DiffSummaryReceiver.lo: subversion/bindings/javahl/native/DiffSummaryReceiver.cpp subversion/bindings/javahl/native/DiffSummaryReceiver.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/DiffSummaryReceiver.cpp + +subversion/bindings/javahl/native/EnumMapper.lo: subversion/bindings/javahl/native/EnumMapper.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItemStateFlags.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/EnumMapper.cpp + +subversion/bindings/javahl/native/File.lo: subversion/bindings/javahl/native/File.cpp subversion/bindings/javahl/native/File.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/File.cpp + +subversion/bindings/javahl/native/ImportFilterCallback.lo: subversion/bindings/javahl/native/ImportFilterCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/ImportFilterCallback.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ImportFilterCallback.cpp + +subversion/bindings/javahl/native/InfoCallback.lo: subversion/bindings/javahl/native/InfoCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/InfoCallback.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/InfoCallback.cpp + +subversion/bindings/javahl/native/InputStream.lo: subversion/bindings/javahl/native/InputStream.cpp subversion/bindings/javahl/native/InputStream.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/InputStream.cpp + +subversion/bindings/javahl/native/JNIByteArray.lo: subversion/bindings/javahl/native/JNIByteArray.cpp subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIByteArray.cpp + +subversion/bindings/javahl/native/JNICriticalSection.lo: subversion/bindings/javahl/native/JNICriticalSection.cpp subversion/bindings/javahl/native/JNICriticalSection.h subversion/bindings/javahl/native/JNIMutex.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNICriticalSection.cpp + +subversion/bindings/javahl/native/JNIMutex.lo: subversion/bindings/javahl/native/JNIMutex.cpp subversion/bindings/javahl/native/JNIMutex.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIMutex.cpp + +subversion/bindings/javahl/native/JNIStackElement.lo: subversion/bindings/javahl/native/JNIStackElement.cpp subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIThreadData.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIStackElement.cpp + +subversion/bindings/javahl/native/JNIStringHolder.lo: subversion/bindings/javahl/native/JNIStringHolder.cpp subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIStringHolder.cpp + +subversion/bindings/javahl/native/JNIThreadData.lo: subversion/bindings/javahl/native/JNIThreadData.cpp subversion/bindings/javahl/native/JNIThreadData.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIThreadData.cpp + +subversion/bindings/javahl/native/JNIUtil.lo: subversion/bindings/javahl/native/JNIUtil.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNICriticalSection.h subversion/bindings/javahl/native/JNIMutex.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIThreadData.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIUtil.cpp + +subversion/bindings/javahl/native/ListCallback.lo: subversion/bindings/javahl/native/ListCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/ListCallback.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ListCallback.cpp + +subversion/bindings/javahl/native/LogMessageCallback.lo: subversion/bindings/javahl/native/LogMessageCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/LogMessageCallback.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/LogMessageCallback.cpp + +subversion/bindings/javahl/native/MessageReceiver.lo: subversion/bindings/javahl/native/MessageReceiver.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/MessageReceiver.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/MessageReceiver.cpp + +subversion/bindings/javahl/native/OutputStream.lo: subversion/bindings/javahl/native/OutputStream.cpp subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/OutputStream.cpp + +subversion/bindings/javahl/native/PatchCallback.lo: subversion/bindings/javahl/native/PatchCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/PatchCallback.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/PatchCallback.cpp + +subversion/bindings/javahl/native/Path.lo: subversion/bindings/javahl/native/Path.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Path.cpp + +subversion/bindings/javahl/native/Pool.lo: subversion/bindings/javahl/native/Pool.cpp subversion/bindings/javahl/native/JNICriticalSection.h subversion/bindings/javahl/native/JNIMutex.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Pool.cpp + +subversion/bindings/javahl/native/Prompter.lo: subversion/bindings/javahl/native/Prompter.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_UserPasswordCallback.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Prompter.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Prompter.cpp + +subversion/bindings/javahl/native/ProplistCallback.lo: subversion/bindings/javahl/native/ProplistCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ProplistCallback.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ProplistCallback.cpp + +subversion/bindings/javahl/native/ReposFreezeAction.lo: subversion/bindings/javahl/native/ReposFreezeAction.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ReposFreezeAction.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ReposFreezeAction.cpp + +subversion/bindings/javahl/native/ReposNotifyCallback.lo: subversion/bindings/javahl/native/ReposNotifyCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ReposNotifyCallback.h subversion/bindings/javahl/native/RevisionRange.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ReposNotifyCallback.cpp + +subversion/bindings/javahl/native/Revision.lo: subversion/bindings/javahl/native/Revision.cpp subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Revision.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Revision.cpp + +subversion/bindings/javahl/native/RevisionRange.lo: subversion/bindings/javahl/native/RevisionRange.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/RevisionRange.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/RevisionRange.cpp + +subversion/bindings/javahl/native/RevpropTable.lo: subversion/bindings/javahl/native/RevpropTable.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/RevpropTable.h subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/RevpropTable.cpp + +subversion/bindings/javahl/native/SVNBase.lo: subversion/bindings/javahl/native/SVNBase.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/SVNBase.cpp + +subversion/bindings/javahl/native/SVNClient.lo: subversion/bindings/javahl/native/SVNClient.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/BlameCallback.h subversion/bindings/javahl/native/ChangelistCallback.h subversion/bindings/javahl/native/ClientContext.h subversion/bindings/javahl/native/CommitCallback.h subversion/bindings/javahl/native/CommitMessage.h subversion/bindings/javahl/native/CopySources.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/DiffOptions.h subversion/bindings/javahl/native/DiffSummaryReceiver.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/ImportFilterCallback.h subversion/bindings/javahl/native/InfoCallback.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/ListCallback.h subversion/bindings/javahl/native/LogMessageCallback.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/PatchCallback.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Prompter.h subversion/bindings/javahl/native/ProplistCallback.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/RevisionRange.h subversion/bindings/javahl/native/RevpropTable.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNClient.h subversion/bindings/javahl/native/StatusCallback.h subversion/bindings/javahl/native/StringArray.h subversion/bindings/javahl/native/Targets.h subversion/bindings/javahl/native/VersionExtended.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/SVNClient.cpp + +subversion/bindings/javahl/native/SVNRepos.lo: subversion/bindings/javahl/native/SVNRepos.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/File.h subversion/bindings/javahl/native/InputStream.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/MessageReceiver.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ReposFreezeAction.h subversion/bindings/javahl/native/ReposNotifyCallback.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNRepos.h subversion/bindings/javahl/native/StringArray.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/SVNRepos.cpp + +subversion/bindings/javahl/native/StatusCallback.lo: subversion/bindings/javahl/native/StatusCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/StatusCallback.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/StatusCallback.cpp + +subversion/bindings/javahl/native/StringArray.lo: subversion/bindings/javahl/native/StringArray.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/StringArray.h subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/StringArray.cpp + +subversion/bindings/javahl/native/Targets.lo: subversion/bindings/javahl/native/Targets.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/StringArray.h subversion/bindings/javahl/native/Targets.h subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Targets.cpp + +subversion/bindings/javahl/native/VersionExtended.lo: subversion/bindings/javahl/native/VersionExtended.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/VersionExtended.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h subversion/include/svn_version.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/VersionExtended.cpp + +subversion/bindings/javahl/native/libsvnjavahl.la.lo: subversion/bindings/javahl/native/libsvnjavahl.la.c + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/libsvnjavahl.la.c + +subversion/bindings/javahl/native/org_apache_subversion_javahl_NativeResources.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_NativeResources.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_NativeResources.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_NativeResources.cpp + +subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNClient.h subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/BlameCallback.h subversion/bindings/javahl/native/ChangelistCallback.h subversion/bindings/javahl/native/ClientContext.h subversion/bindings/javahl/native/CommitCallback.h subversion/bindings/javahl/native/CommitMessage.h subversion/bindings/javahl/native/CopySources.h subversion/bindings/javahl/native/DiffOptions.h subversion/bindings/javahl/native/DiffSummaryReceiver.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/ImportFilterCallback.h subversion/bindings/javahl/native/InfoCallback.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/ListCallback.h subversion/bindings/javahl/native/LogMessageCallback.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/PatchCallback.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Prompter.h subversion/bindings/javahl/native/ProplistCallback.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/RevisionRange.h subversion/bindings/javahl/native/RevpropTable.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNClient.h subversion/bindings/javahl/native/StatusCallback.h subversion/bindings/javahl/native/StringArray.h subversion/bindings/javahl/native/Targets.h subversion/bindings/javahl/native/VersionExtended.h subversion/bindings/javahl/native/version.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.cpp + +subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNRepos.h subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/File.h subversion/bindings/javahl/native/InputStream.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/MessageReceiver.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ReposFreezeAction.h subversion/bindings/javahl/native/ReposNotifyCallback.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNRepos.h subversion/bindings/javahl/native/StringArray.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.cpp + +subversion/bindings/javahl/native/org_apache_subversion_javahl_types_Version.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_types_Version.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Version.h subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h subversion/include/svn_version.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_types_Version.cpp + +subversion/bindings/javahl/native/org_apache_subversion_javahl_types_VersionExtended.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_types_VersionExtended.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LinkedLib.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LinkedLibIterator.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LoadedLib.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LoadedLibIterator.h subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/VersionExtended.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h subversion/include/svn_version.h + $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_types_VersionExtended.cpp + +subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.lo: subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/proxy/swig_perl_external_runtime.swg subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_SWIG_PL) $(canonicalized_srcdir)subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c + +subversion/bindings/swig/perl/native/core.lo: subversion/bindings/swig/perl/native/core.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/core.c + +subversion/bindings/swig/perl/native/svn_client.lo: subversion/bindings/swig/perl/native/svn_client.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h + $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_client.c + +subversion/bindings/swig/perl/native/svn_delta.lo: subversion/bindings/swig/perl/native/svn_delta.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_delta.c + +subversion/bindings/swig/perl/native/svn_diff.lo: subversion/bindings/swig/perl/native/svn_diff.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_diff.c + +subversion/bindings/swig/perl/native/svn_fs.lo: subversion/bindings/swig/perl/native/svn_fs.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_fs.c + +subversion/bindings/swig/perl/native/svn_ra.lo: subversion/bindings/swig/perl/native/svn_ra.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_ra.c + +subversion/bindings/swig/perl/native/svn_repos.lo: subversion/bindings/swig/perl/native/svn_repos.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_repos.c + +subversion/bindings/swig/perl/native/svn_wc.lo: subversion/bindings/swig/perl/native/svn_wc.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h + $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_wc.c + +subversion/bindings/swig/python/core.lo: subversion/bindings/swig/python/core.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/core.c + +subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.lo: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c subversion/bindings/swig/proxy/swig_python_external_runtime.swg subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_SWIG_PY) $(canonicalized_srcdir)subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c + +subversion/bindings/swig/python/svn_client.lo: subversion/bindings/swig/python/svn_client.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h + $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_client.c + +subversion/bindings/swig/python/svn_delta.lo: subversion/bindings/swig/python/svn_delta.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_delta.c + +subversion/bindings/swig/python/svn_diff.lo: subversion/bindings/swig/python/svn_diff.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_diff.c + +subversion/bindings/swig/python/svn_fs.lo: subversion/bindings/swig/python/svn_fs.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_fs.c + +subversion/bindings/swig/python/svn_ra.lo: subversion/bindings/swig/python/svn_ra.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_ra.c + +subversion/bindings/swig/python/svn_repos.lo: subversion/bindings/swig/python/svn_repos.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_repos.c + +subversion/bindings/swig/python/svn_wc.lo: subversion/bindings/swig/python/svn_wc.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h + $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_wc.c + +subversion/bindings/swig/ruby/core.lo: subversion/bindings/swig/ruby/core.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/core.c + +subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.lo: subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.c subversion/bindings/swig/proxy/swig_ruby_external_runtime.swg subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_nls.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_SWIG_RB) $(canonicalized_srcdir)subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.c + +subversion/bindings/swig/ruby/svn_client.lo: subversion/bindings/swig/ruby/svn_client.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h + $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_client.c + +subversion/bindings/swig/ruby/svn_delta.lo: subversion/bindings/swig/ruby/svn_delta.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_delta.c + +subversion/bindings/swig/ruby/svn_diff.lo: subversion/bindings/swig/ruby/svn_diff.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_diff.c + +subversion/bindings/swig/ruby/svn_fs.lo: subversion/bindings/swig/ruby/svn_fs.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_fs.c + +subversion/bindings/swig/ruby/svn_ra.lo: subversion/bindings/swig/ruby/svn_ra.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_ra.c + +subversion/bindings/swig/ruby/svn_repos.lo: subversion/bindings/swig/ruby/svn_repos.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_wc.h subversion/svn_private_config.h + $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_repos.c + +subversion/bindings/swig/ruby/svn_wc.lo: subversion/bindings/swig/ruby/svn_wc.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h + $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_wc.c + +subversion/libsvn_auth_gnome_keyring/gnome_keyring.lo: subversion/libsvn_auth_gnome_keyring/gnome_keyring.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_auth_gnome_keyring/version.lo: subversion/libsvn_auth_gnome_keyring/version.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h + +subversion/libsvn_auth_kwallet/kwallet.lo: subversion/libsvn_auth_kwallet/kwallet.cpp subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/svn_private_config.h + +subversion/libsvn_auth_kwallet/version.lo: subversion/libsvn_auth_kwallet/version.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h + +subversion/libsvn_client/add.lo: subversion/libsvn_client/add.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/blame.lo: subversion/libsvn_client/blame.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/cat.lo: subversion/libsvn_client/cat.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/changelist.lo: subversion/libsvn_client/changelist.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/checkout.lo: subversion/libsvn_client/checkout.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/cleanup.lo: subversion/libsvn_client/cleanup.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/cmdline.lo: subversion/libsvn_client/cmdline.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/commit.lo: subversion/libsvn_client/commit.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/commit_util.lo: subversion/libsvn_client/commit_util.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/compat_providers.lo: subversion/libsvn_client/compat_providers.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + +subversion/libsvn_client/copy.lo: subversion/libsvn_client/copy.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h + +subversion/libsvn_client/copy_foreign.lo: subversion/libsvn_client/copy_foreign.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/ctx.lo: subversion/libsvn_client/ctx.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h + +subversion/libsvn_client/delete.lo: subversion/libsvn_client/delete.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/deprecated.lo: subversion/libsvn_client/deprecated.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h + +subversion/libsvn_client/diff.lo: subversion/libsvn_client/diff.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_private.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/diff_local.lo: subversion/libsvn_client/diff_local.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/diff_summarize.lo: subversion/libsvn_client/diff_summarize.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h + +subversion/libsvn_client/export.lo: subversion/libsvn_client/export.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/externals.lo: subversion/libsvn_client/externals.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/import.lo: subversion/libsvn_client/import.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/info.lo: subversion/libsvn_client/info.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/iprops.lo: subversion/libsvn_client/iprops.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/list.lo: subversion/libsvn_client/list.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/locking_commands.lo: subversion/libsvn_client/locking_commands.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/log.lo: subversion/libsvn_client/log.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/merge.lo: subversion/libsvn_client/merge.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h + +subversion/libsvn_client/mergeinfo.lo: subversion/libsvn_client/mergeinfo.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h + +subversion/libsvn_client/patch.lo: subversion/libsvn_client/patch.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_eol_private.h subversion/include/private/svn_magic.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/prop_commands.lo: subversion/libsvn_client/prop_commands.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/ra.lo: subversion/libsvn_client/ra.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h + +subversion/libsvn_client/relocate.lo: subversion/libsvn_client/relocate.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/repos_diff.lo: subversion/libsvn_client/repos_diff.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/resolved.lo: subversion/libsvn_client/resolved.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/revert.lo: subversion/libsvn_client/revert.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/revisions.lo: subversion/libsvn_client/revisions.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/status.lo: subversion/libsvn_client/status.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/switch.lo: subversion/libsvn_client/switch.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/update.lo: subversion/libsvn_client/update.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/upgrade.lo: subversion/libsvn_client/upgrade.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/url.lo: subversion/libsvn_client/url.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/util.lo: subversion/libsvn_client/util.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h + +subversion/libsvn_client/version.lo: subversion/libsvn_client/version.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h + +subversion/libsvn_delta/cancel.lo: subversion/libsvn_delta/cancel.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_delta/compat.lo: subversion/libsvn_delta/compat.c subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_editor.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_delta/compose_delta.lo: subversion/libsvn_delta/compose_delta.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h + +subversion/libsvn_delta/debug_editor.lo: subversion/libsvn_delta/debug_editor.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/debug_editor.h + +subversion/libsvn_delta/default_editor.lo: subversion/libsvn_delta/default_editor.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_delta/deprecated.lo: subversion/libsvn_delta/deprecated.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_delta/depth_filter_editor.lo: subversion/libsvn_delta/depth_filter_editor.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_delta/editor.lo: subversion/libsvn_delta/editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_delta/path_driver.lo: subversion/libsvn_delta/path_driver.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_delta/svndiff.lo: subversion/libsvn_delta/svndiff.c subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_editor.h subversion/include/private/svn_error_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h subversion/svn_private_config.h + +subversion/libsvn_delta/text_delta.lo: subversion/libsvn_delta/text_delta.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h + +subversion/libsvn_delta/version.lo: subversion/libsvn_delta/version.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h + +subversion/libsvn_delta/xdelta.lo: subversion/libsvn_delta/xdelta.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h + +subversion/libsvn_diff/deprecated.lo: subversion/libsvn_diff/deprecated.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_diff/diff.lo: subversion/libsvn_diff/diff.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h + +subversion/libsvn_diff/diff3.lo: subversion/libsvn_diff/diff3.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h + +subversion/libsvn_diff/diff4.lo: subversion/libsvn_diff/diff4.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h + +subversion/libsvn_diff/diff_file.lo: subversion/libsvn_diff/diff_file.c subversion/include/private/svn_adler32.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_private.h subversion/include/private/svn_eol_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_diff/diff.h subversion/svn_private_config.h + +subversion/libsvn_diff/diff_memory.lo: subversion/libsvn_diff/diff_memory.c subversion/include/private/svn_adler32.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_private.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_diff/diff.h subversion/svn_private_config.h + +subversion/libsvn_diff/diff_tree.lo: subversion/libsvn_diff/diff_tree.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_diff/lcs.lo: subversion/libsvn_diff/lcs.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h + +subversion/libsvn_diff/parse-diff.lo: subversion/libsvn_diff/parse-diff.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_eol_private.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h + +subversion/libsvn_diff/token.lo: subversion/libsvn_diff/token.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h + +subversion/libsvn_diff/util.lo: subversion/libsvn_diff/util.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_diff/diff.h subversion/svn_private_config.h + +subversion/libsvn_fs/access.lo: subversion/libsvn_fs/access.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h + +subversion/libsvn_fs/editor.lo: subversion/libsvn_fs/editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/svn_private_config.h + +subversion/libsvn_fs/fs-loader.lo: subversion/libsvn_fs/fs-loader.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_mutex.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_fs/fs-loader.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/bdb-err.lo: subversion/libsvn_fs_base/bdb/bdb-err.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/bdb_compat.lo: subversion/libsvn_fs_base/bdb/bdb_compat.c subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/changes-table.lo: subversion/libsvn_fs_base/bdb/changes-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/checksum-reps-table.lo: subversion/libsvn_fs_base/bdb/checksum-reps-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/checksum-reps-table.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/copies-table.lo: subversion/libsvn_fs_base/bdb/copies-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/dbt.lo: subversion/libsvn_fs_base/bdb/dbt.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/id.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/env.lo: subversion/libsvn_fs_base/bdb/env.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mutex.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/lock-tokens-table.lo: subversion/libsvn_fs_base/bdb/lock-tokens-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/lock-tokens-table.h subversion/libsvn_fs_base/bdb/locks-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/locks-table.lo: subversion/libsvn_fs_base/bdb/locks-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/lock-tokens-table.h subversion/libsvn_fs_base/bdb/locks-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/miscellaneous-table.lo: subversion/libsvn_fs_base/bdb/miscellaneous-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/miscellaneous-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/node-origins-table.lo: subversion/libsvn_fs_base/bdb/node-origins-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/nodes-table.lo: subversion/libsvn_fs_base/bdb/nodes-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/reps-table.lo: subversion/libsvn_fs_base/bdb/reps-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/rev-table.lo: subversion/libsvn_fs_base/bdb/rev-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/strings-table.lo: subversion/libsvn_fs_base/bdb/strings-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/txn-table.lo: subversion/libsvn_fs_base/bdb/txn-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/bdb/uuids-table.lo: subversion/libsvn_fs_base/bdb/uuids-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/uuids-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/dag.lo: subversion/libsvn_fs_base/dag.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/checksum-reps-table.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/dag.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/node-rev.h subversion/libsvn_fs_base/reps-strings.h subversion/libsvn_fs_base/revs-txns.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/err.lo: subversion/libsvn_fs_base/err.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/id.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/fs.lo: subversion/libsvn_fs_base/fs.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/checksum-reps-table.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/lock-tokens-table.h subversion/libsvn_fs_base/bdb/locks-table.h subversion/libsvn_fs_base/bdb/miscellaneous-table.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/bdb/uuids-table.h subversion/libsvn_fs_base/dag.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/lock.h subversion/libsvn_fs_base/revs-txns.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/tree.h subversion/libsvn_fs_base/uuid.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/id.lo: subversion/libsvn_fs_base/id.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/id.h + +subversion/libsvn_fs_base/key-gen.lo: subversion/libsvn_fs_base/key-gen.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_base/key-gen.h + +subversion/libsvn_fs_base/lock.lo: subversion/libsvn_fs_base/lock.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/lock-tokens-table.h subversion/libsvn_fs_base/bdb/locks-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/lock.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/tree.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/node-rev.lo: subversion/libsvn_fs_base/node-rev.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/node-rev.h subversion/libsvn_fs_base/reps-strings.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/reps-strings.lo: subversion/libsvn_fs_base/reps-strings.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/reps-strings.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/revs-txns.lo: subversion/libsvn_fs_base/revs-txns.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/dag.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/revs-txns.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/tree.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/trail.lo: subversion/libsvn_fs_base/trail.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/tree.lo: subversion/libsvn_fs_base/tree.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/miscellaneous-table.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/dag.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/lock.h subversion/libsvn_fs_base/node-rev.h subversion/libsvn_fs_base/revs-txns.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/tree.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/util/fs_skels.lo: subversion/libsvn_fs_base/util/fs_skels.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h + +subversion/libsvn_fs_base/uuid.lo: subversion/libsvn_fs_base/uuid.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/uuids-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/uuid.h subversion/svn_private_config.h + +subversion/libsvn_fs_fs/caching.lo: subversion/libsvn_fs_fs/caching.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/dag.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/temp_serializer.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h + +subversion/libsvn_fs_fs/dag.lo: subversion/libsvn_fs_fs/dag.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_temp_serializer.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/dag.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/key-gen.h subversion/libsvn_fs_fs/temp_serializer.h subversion/svn_private_config.h + +subversion/libsvn_fs_fs/fs.lo: subversion/libsvn_fs_fs/fs.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/lock.h subversion/libsvn_fs_fs/rep-cache.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h + +subversion/libsvn_fs_fs/fs_fs.lo: subversion/libsvn_fs_fs/fs_fs.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/key-gen.h subversion/libsvn_fs_fs/lock.h subversion/libsvn_fs_fs/rep-cache.h subversion/libsvn_fs_fs/temp_serializer.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h + +subversion/libsvn_fs_fs/id.lo: subversion/libsvn_fs_fs/id.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/private/svn_temp_serializer.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/id.h + +subversion/libsvn_fs_fs/key-gen.lo: subversion/libsvn_fs_fs/key-gen.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_fs/key-gen.h + +subversion/libsvn_fs_fs/lock.lo: subversion/libsvn_fs_fs/lock.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/lock.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h + +subversion/libsvn_fs_fs/rep-cache.lo: subversion/libsvn_fs_fs/rep-cache.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/rep-cache-db.h subversion/libsvn_fs_fs/rep-cache.h subversion/svn_private_config.h + +subversion/libsvn_fs_fs/temp_serializer.lo: subversion/libsvn_fs_fs/temp_serializer.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_temp_serializer.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/temp_serializer.h + +subversion/libsvn_fs_fs/tree.lo: subversion/libsvn_fs_fs/tree.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/dag.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/key-gen.h subversion/libsvn_fs_fs/lock.h subversion/libsvn_fs_fs/temp_serializer.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h + +subversion/libsvn_fs_util/fs-util.lo: subversion/libsvn_fs_util/fs-util.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/svn_private_config.h + +subversion/libsvn_ra/compat.lo: subversion/libsvn_ra/compat.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra/ra_loader.h subversion/svn_private_config.h + +subversion/libsvn_ra/debug_reporter.lo: subversion/libsvn_ra/debug_reporter.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra/debug_reporter.h + +subversion/libsvn_ra/deprecated.lo: subversion/libsvn_ra/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra/deprecated.h subversion/libsvn_ra/ra_loader.h subversion/svn_private_config.h + +subversion/libsvn_ra/editor.lo: subversion/libsvn_ra/editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra/ra_loader.h subversion/svn_private_config.h + +subversion/libsvn_ra/ra_loader.lo: subversion/libsvn_ra/ra_loader.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/deprecated.h subversion/libsvn_ra/ra_loader.h subversion/svn_private_config.h + +subversion/libsvn_ra/util.lo: subversion/libsvn_ra/util.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_ra_local/ra_plugin.lo: subversion/libsvn_ra_local/ra_plugin.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_repos_private.h subversion/include/svn_auth.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra/wrapper_template.h subversion/libsvn_ra_local/ra_local.h subversion/svn_private_config.h + +subversion/libsvn_ra_local/split_url.lo: subversion/libsvn_ra_local/split_url.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_local/ra_local.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/blame.lo: subversion/libsvn_ra_serf/blame.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/blncache.lo: subversion/libsvn_ra_serf/blncache.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_serf/blncache.h + +subversion/libsvn_ra_serf/commit.lo: subversion/libsvn_ra_serf/commit.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/get_deleted_rev.lo: subversion/libsvn_ra_serf/get_deleted_rev.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/getdate.lo: subversion/libsvn_ra_serf/getdate.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/getlocations.lo: subversion/libsvn_ra_serf/getlocations.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/getlocationsegments.lo: subversion/libsvn_ra_serf/getlocationsegments.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/getlocks.lo: subversion/libsvn_ra_serf/getlocks.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/inherited_props.lo: subversion/libsvn_ra_serf/inherited_props.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/locks.lo: subversion/libsvn_ra_serf/locks.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/log.lo: subversion/libsvn_ra_serf/log.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/merge.lo: subversion/libsvn_ra_serf/merge.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/mergeinfo.lo: subversion/libsvn_ra_serf/mergeinfo.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/options.lo: subversion/libsvn_ra_serf/options.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/property.lo: subversion/libsvn_ra_serf/property.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/replay.lo: subversion/libsvn_ra_serf/replay.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/sb_bucket.lo: subversion/libsvn_ra_serf/sb_bucket.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/serf.lo: subversion/libsvn_ra_serf/serf.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra/wrapper_template.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/update.lo: subversion/libsvn_ra_serf/update.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/util.lo: subversion/libsvn_ra_serf/util.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_serf/util_error.lo: subversion/libsvn_ra_serf/util_error.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_error_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h + +subversion/libsvn_ra_serf/xml.lo: subversion/libsvn_ra_serf/xml.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h + +subversion/libsvn_ra_svn/client.lo: subversion/libsvn_ra_svn/client.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra/wrapper_template.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h + +subversion/libsvn_ra_svn/cram.lo: subversion/libsvn_ra_svn/cram.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h + +subversion/libsvn_ra_svn/cyrus_auth.lo: subversion/libsvn_ra_svn/cyrus_auth.c subversion/include/private/ra_svn_sasl.h subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_mutex.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h + +subversion/libsvn_ra_svn/deprecated.lo: subversion/libsvn_ra_svn/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_ra_svn/editorp.lo: subversion/libsvn_ra_svn/editorp.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h + +subversion/libsvn_ra_svn/internal_auth.lo: subversion/libsvn_ra_svn/internal_auth.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h + +subversion/libsvn_ra_svn/marshal.lo: subversion/libsvn_ra_svn/marshal.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_error_private.h subversion/include/private/svn_ra_svn_private.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h + +subversion/libsvn_ra_svn/streams.lo: subversion/libsvn_ra_svn/streams.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h + +subversion/libsvn_ra_svn/version.lo: subversion/libsvn_ra_svn/version.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h + +subversion/libsvn_repos/authz.lo: subversion/libsvn_repos/authz.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h + +subversion/libsvn_repos/commit.lo: subversion/libsvn_repos/commit.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fspath.h subversion/include/private/svn_repos_private.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/delta.lo: subversion/libsvn_repos/delta.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/deprecated.lo: subversion/libsvn_repos/deprecated.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/dump.lo: subversion/libsvn_repos/dump.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_repos/fs-wrap.lo: subversion/libsvn_repos/fs-wrap.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/hooks.lo: subversion/libsvn_repos/hooks.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_string_private.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/load-fs-vtable.lo: subversion/libsvn_repos/load-fs-vtable.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/load.lo: subversion/libsvn_repos/load.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/log.lo: subversion/libsvn_repos/log.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/node_tree.lo: subversion/libsvn_repos/node_tree.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/notify.lo: subversion/libsvn_repos/notify.c subversion/include/private/svn_debug.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/replay.lo: subversion/libsvn_repos/replay.c subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_repos_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_repos/reporter.lo: subversion/libsvn_repos/reporter.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_fspath.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/repos.lo: subversion/libsvn_repos/repos.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_repos/rev_hunt.lo: subversion/libsvn_repos/rev_hunt.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h + +subversion/libsvn_subr/adler32.lo: subversion/libsvn_subr/adler32.c subversion/include/private/svn_adler32.h + +subversion/libsvn_subr/atomic.lo: subversion/libsvn_subr/atomic.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h + +subversion/libsvn_subr/auth.lo: subversion/libsvn_subr/auth.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_subr/auth.h subversion/svn_private_config.h + +subversion/libsvn_subr/base64.lo: subversion/libsvn_subr/base64.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/cache-inprocess.lo: subversion/libsvn_subr/cache-inprocess.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_mutex.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/cache.h subversion/svn_private_config.h + +subversion/libsvn_subr/cache-membuffer.lo: subversion/libsvn_subr/cache-membuffer.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mutex.h subversion/include/private/svn_pseudo_md5.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/cache.h subversion/libsvn_subr/md5.h subversion/svn_private_config.h + +subversion/libsvn_subr/cache-memcache.lo: subversion/libsvn_subr/cache-memcache.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/cache.h subversion/svn_private_config.h + +subversion/libsvn_subr/cache.lo: subversion/libsvn_subr/cache.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/cache.h + +subversion/libsvn_subr/cache_config.lo: subversion/libsvn_subr/cache_config.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/checksum.lo: subversion/libsvn_subr/checksum.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/md5.h subversion/libsvn_subr/sha1.h subversion/svn_private_config.h + +subversion/libsvn_subr/cmdline.lo: subversion/libsvn_subr/cmdline.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_nls.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_xml.h subversion/libsvn_subr/win32_crashrpt.h subversion/svn_private_config.h + +subversion/libsvn_subr/compat.lo: subversion/libsvn_subr/compat.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/config.lo: subversion/libsvn_subr/config.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/config_impl.h subversion/svn_private_config.h + +subversion/libsvn_subr/config_auth.lo: subversion/libsvn_subr/config_auth.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/auth.h subversion/libsvn_subr/config_impl.h subversion/svn_private_config.h + +subversion/libsvn_subr/config_file.lo: subversion/libsvn_subr/config_file.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/libsvn_subr/config_impl.h subversion/svn_private_config.h + +subversion/libsvn_subr/config_win.lo: subversion/libsvn_subr/config_win.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_subr/config_impl.h subversion/svn_private_config.h + +subversion/libsvn_subr/crypto.lo: subversion/libsvn_subr/crypto.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/crypto.h subversion/svn_private_config.h + +subversion/libsvn_subr/ctype.lo: subversion/libsvn_subr/ctype.c subversion/include/svn_ctype.h + +subversion/libsvn_subr/date.lo: subversion/libsvn_subr/date.c subversion/include/private/svn_debug.h subversion/include/private/svn_token.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/debug.lo: subversion/libsvn_subr/debug.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/deprecated.lo: subversion/libsvn_subr/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_subr/opt.h subversion/svn_private_config.h + +subversion/libsvn_subr/dirent_uri.lo: subversion/libsvn_subr/dirent_uri.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/dirent_uri.h subversion/svn_private_config.h + +subversion/libsvn_subr/dso.lo: subversion/libsvn_subr/dso.c subversion/include/private/svn_debug.h subversion/include/private/svn_mutex.h subversion/include/svn_checksum.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/eol.lo: subversion/libsvn_subr/eol.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_eol_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/error.lo: subversion/libsvn_subr/error.c subversion/include/private/svn_debug.h subversion/include/private/svn_error_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_subr/gpg_agent.lo: subversion/libsvn_subr/gpg_agent.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/hash.lo: subversion/libsvn_subr/hash.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/io.lo: subversion/libsvn_subr/io.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_io_private.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_subr/iter.lo: subversion/libsvn_subr/iter.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_types.h + +subversion/libsvn_subr/lock.lo: subversion/libsvn_subr/lock.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h + +subversion/libsvn_subr/log.lo: subversion/libsvn_subr/log.c subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/macos_keychain.lo: subversion/libsvn_subr/macos_keychain.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_subr/magic.lo: subversion/libsvn_subr/magic.c subversion/include/private/svn_debug.h subversion/include/private/svn_magic.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/md5.lo: subversion/libsvn_subr/md5.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_md5.h subversion/include/svn_types.h subversion/libsvn_subr/md5.h + +subversion/libsvn_subr/mergeinfo.lo: subversion/libsvn_subr/mergeinfo.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/mutex.lo: subversion/libsvn_subr/mutex.c subversion/include/private/svn_debug.h subversion/include/private/svn_mutex.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/named_atomic.lo: subversion/libsvn_subr/named_atomic.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/nls.lo: subversion/libsvn_subr/nls.c subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_nls.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/opt.lo: subversion/libsvn_subr/opt.c subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_subr/opt.h subversion/svn_private_config.h + +subversion/libsvn_subr/path.lo: subversion/libsvn_subr/path.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_subr/dirent_uri.h subversion/svn_private_config.h + +subversion/libsvn_subr/pool.lo: subversion/libsvn_subr/pool.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h + +subversion/libsvn_subr/prompt.lo: subversion/libsvn_subr/prompt.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/properties.lo: subversion/libsvn_subr/properties.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/pseudo_md5.lo: subversion/libsvn_subr/pseudo_md5.c subversion/include/private/svn_pseudo_md5.h + +subversion/libsvn_subr/quoprint.lo: subversion/libsvn_subr/quoprint.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_quoprint.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/sha1.lo: subversion/libsvn_subr/sha1.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/libsvn_subr/sha1.h + +subversion/libsvn_subr/simple_providers.lo: subversion/libsvn_subr/simple_providers.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/libsvn_subr/auth.h subversion/svn_private_config.h + +subversion/libsvn_subr/skel.lo: subversion/libsvn_subr/skel.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/private/svn_string_private.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/sorts.lo: subversion/libsvn_subr/sorts.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/spillbuf.lo: subversion/libsvn_subr/spillbuf.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/sqlite.lo: subversion/libsvn_subr/sqlite.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/internal_statements.h subversion/svn_private_config.h + +subversion/libsvn_subr/sqlite3wrapper.lo: subversion/libsvn_subr/sqlite3wrapper.c subversion/svn_private_config.h + +subversion/libsvn_subr/ssl_client_cert_providers.lo: subversion/libsvn_subr/ssl_client_cert_providers.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/ssl_client_cert_pw_providers.lo: subversion/libsvn_subr/ssl_client_cert_pw_providers.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/ssl_server_trust_providers.lo: subversion/libsvn_subr/ssl_server_trust_providers.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/stream.lo: subversion/libsvn_subr/stream.c subversion/include/private/svn_debug.h subversion/include/private/svn_eol_private.h subversion/include/private/svn_error_private.h subversion/include/private/svn_io_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_subr/string.lo: subversion/libsvn_subr/string.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_string_private.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/subst.lo: subversion/libsvn_subr/subst.c subversion/include/private/svn_debug.h subversion/include/private/svn_io_private.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_subr/sysinfo.lo: subversion/libsvn_subr/sysinfo.c subversion/include/private/svn_debug.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_subr/sysinfo.h subversion/svn_private_config.h + +subversion/libsvn_subr/target.lo: subversion/libsvn_subr/target.c subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/temp_serializer.lo: subversion/libsvn_subr/temp_serializer.c subversion/include/private/svn_debug.h subversion/include/private/svn_temp_serializer.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h + +subversion/libsvn_subr/time.lo: subversion/libsvn_subr/time.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_subr/token.lo: subversion/libsvn_subr/token.c subversion/include/private/svn_debug.h subversion/include/private/svn_token.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/types.lo: subversion/libsvn_subr/types.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/user.lo: subversion/libsvn_subr/user.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h + +subversion/libsvn_subr/username_providers.lo: subversion/libsvn_subr/username_providers.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h + +subversion/libsvn_subr/utf.lo: subversion/libsvn_subr/utf.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mutex.h subversion/include/private/svn_string_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_subr/win32_xlate.h subversion/svn_private_config.h + +subversion/libsvn_subr/utf_validate.lo: subversion/libsvn_subr/utf_validate.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_eol_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h + +subversion/libsvn_subr/utf_width.lo: subversion/libsvn_subr/utf_width.c subversion/include/private/svn_debug.h subversion/include/private/svn_utf_private.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_subr/validate.lo: subversion/libsvn_subr/validate.c subversion/include/private/svn_debug.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/libsvn_subr/version.lo: subversion/libsvn_subr/version.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_subr/sysinfo.h subversion/svn_private_config.h + +subversion/libsvn_subr/win32_crashrpt.lo: subversion/libsvn_subr/win32_crashrpt.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_subr/win32_crashrpt.h subversion/libsvn_subr/win32_crashrpt_dll.h + +subversion/libsvn_subr/win32_crypto.lo: subversion/libsvn_subr/win32_crypto.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/svn_private_config.h + +subversion/libsvn_subr/win32_xlate.lo: subversion/libsvn_subr/win32_xlate.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_subr/win32_xlate.h + +subversion/libsvn_subr/xml.lo: subversion/libsvn_subr/xml.c subversion/include/private/svn_debug.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/svn_private_config.h + +subversion/libsvn_wc/adm_crawler.lo: subversion/libsvn_wc/adm_crawler.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/adm_files.lo: subversion/libsvn_wc/adm_files.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/adm_ops.lo: subversion/libsvn_wc/adm_ops.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/ambient_depth_filter_editor.lo: subversion/libsvn_wc/ambient_depth_filter_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/cleanup.lo: subversion/libsvn_wc/cleanup.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/conflicts.lo: subversion/libsvn_wc/conflicts.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_string_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/context.lo: subversion/libsvn_wc/context.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/copy.lo: subversion/libsvn_wc/copy.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/crop.lo: subversion/libsvn_wc/crop.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/delete.lo: subversion/libsvn_wc/delete.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/deprecated.lo: subversion/libsvn_wc/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/diff_editor.lo: subversion/libsvn_wc/diff_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/diff.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/diff_local.lo: subversion/libsvn_wc/diff_local.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/diff.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/entries.lo: subversion/libsvn_wc/entries.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/externals.lo: subversion/libsvn_wc/externals.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/info.lo: subversion/libsvn_wc/info.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/lock.lo: subversion/libsvn_wc/lock.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/merge.lo: subversion/libsvn_wc/merge.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/node.lo: subversion/libsvn_wc/node.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/old-and-busted.lo: subversion/libsvn_wc/old-and-busted.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/props.lo: subversion/libsvn_wc/props.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/questions.lo: subversion/libsvn_wc/questions.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/relocate.lo: subversion/libsvn_wc/relocate.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/revert.lo: subversion/libsvn_wc/revert.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_io_private.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/revision_status.lo: subversion/libsvn_wc/revision_status.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/status.lo: subversion/libsvn_wc/status.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/translate.lo: subversion/libsvn_wc/translate.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/tree_conflicts.lo: subversion/libsvn_wc/tree_conflicts.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/update_editor.lo: subversion/libsvn_wc/update_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/upgrade.lo: subversion/libsvn_wc/upgrade.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/props.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/util.lo: subversion/libsvn_wc/util.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/wc_db.lo: subversion/libsvn_wc/wc_db.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/token-map.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/wc_db_pristine.lo: subversion/libsvn_wc/wc_db_pristine.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h + +subversion/libsvn_wc/wc_db_update_move.lo: subversion/libsvn_wc/wc_db_update_move.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/token-map.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/libsvn_wc/wc_db_util.lo: subversion/libsvn_wc/wc_db_util.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h + +subversion/libsvn_wc/wc_db_wcroot.lo: subversion/libsvn_wc/wc_db_wcroot.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h + +subversion/libsvn_wc/wcroot_anchor.lo: subversion/libsvn_wc/wcroot_anchor.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/libsvn_wc/workqueue.lo: subversion/libsvn_wc/workqueue.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h + +subversion/mod_authz_svn/mod_authz_svn.lo: subversion/mod_authz_svn/mod_authz_svn.c subversion/include/mod_authz_svn.h subversion/include/mod_dav_svn.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_authz_svn/mod_authz_svn.c ; else echo "fake" > subversion/mod_authz_svn/mod_authz_svn.lo ; fi + +subversion/mod_dav_svn/activity.lo: subversion/mod_dav_svn/activity.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/activity.c ; else echo "fake" > subversion/mod_dav_svn/activity.lo ; fi + +subversion/mod_dav_svn/authz.lo: subversion/mod_dav_svn/authz.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/authz.c ; else echo "fake" > subversion/mod_dav_svn/authz.lo ; fi + +subversion/mod_dav_svn/deadprops.lo: subversion/mod_dav_svn/deadprops.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/deadprops.c ; else echo "fake" > subversion/mod_dav_svn/deadprops.lo ; fi + +subversion/mod_dav_svn/liveprops.lo: subversion/mod_dav_svn/liveprops.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/liveprops.c ; else echo "fake" > subversion/mod_dav_svn/liveprops.lo ; fi + +subversion/mod_dav_svn/lock.lo: subversion/mod_dav_svn/lock.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/lock.c ; else echo "fake" > subversion/mod_dav_svn/lock.lo ; fi + +subversion/mod_dav_svn/merge.lo: subversion/mod_dav_svn/merge.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/merge.c ; else echo "fake" > subversion/mod_dav_svn/merge.lo ; fi + +subversion/mod_dav_svn/mirror.lo: subversion/mod_dav_svn/mirror.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/mirror.c ; else echo "fake" > subversion/mod_dav_svn/mirror.lo ; fi + +subversion/mod_dav_svn/mod_dav_svn.lo: subversion/mod_dav_svn/mod_dav_svn.c subversion/include/mod_authz_svn.h subversion/include/mod_dav_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/mod_dav_svn.c ; else echo "fake" > subversion/mod_dav_svn/mod_dav_svn.lo ; fi + +subversion/mod_dav_svn/posts/create_txn.lo: subversion/mod_dav_svn/posts/create_txn.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/posts/create_txn.c ; else echo "fake" > subversion/mod_dav_svn/posts/create_txn.lo ; fi + +subversion/mod_dav_svn/reports/dated-rev.lo: subversion/mod_dav_svn/reports/dated-rev.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/dated-rev.c ; else echo "fake" > subversion/mod_dav_svn/reports/dated-rev.lo ; fi + +subversion/mod_dav_svn/reports/deleted-rev.lo: subversion/mod_dav_svn/reports/deleted-rev.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/deleted-rev.c ; else echo "fake" > subversion/mod_dav_svn/reports/deleted-rev.lo ; fi + +subversion/mod_dav_svn/reports/file-revs.lo: subversion/mod_dav_svn/reports/file-revs.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/file-revs.c ; else echo "fake" > subversion/mod_dav_svn/reports/file-revs.lo ; fi + +subversion/mod_dav_svn/reports/get-location-segments.lo: subversion/mod_dav_svn/reports/get-location-segments.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/get-location-segments.c ; else echo "fake" > subversion/mod_dav_svn/reports/get-location-segments.lo ; fi + +subversion/mod_dav_svn/reports/get-locations.lo: subversion/mod_dav_svn/reports/get-locations.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/get-locations.c ; else echo "fake" > subversion/mod_dav_svn/reports/get-locations.lo ; fi + +subversion/mod_dav_svn/reports/get-locks.lo: subversion/mod_dav_svn/reports/get-locks.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/get-locks.c ; else echo "fake" > subversion/mod_dav_svn/reports/get-locks.lo ; fi + +subversion/mod_dav_svn/reports/inherited-props.lo: subversion/mod_dav_svn/reports/inherited-props.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/inherited-props.c ; else echo "fake" > subversion/mod_dav_svn/reports/inherited-props.lo ; fi + +subversion/mod_dav_svn/reports/log.lo: subversion/mod_dav_svn/reports/log.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/log.c ; else echo "fake" > subversion/mod_dav_svn/reports/log.lo ; fi + +subversion/mod_dav_svn/reports/mergeinfo.lo: subversion/mod_dav_svn/reports/mergeinfo.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/mergeinfo.c ; else echo "fake" > subversion/mod_dav_svn/reports/mergeinfo.lo ; fi + +subversion/mod_dav_svn/reports/replay.lo: subversion/mod_dav_svn/reports/replay.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/replay.c ; else echo "fake" > subversion/mod_dav_svn/reports/replay.lo ; fi + +subversion/mod_dav_svn/reports/update.lo: subversion/mod_dav_svn/reports/update.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/update.c ; else echo "fake" > subversion/mod_dav_svn/reports/update.lo ; fi + +subversion/mod_dav_svn/repos.lo: subversion/mod_dav_svn/repos.c subversion/include/mod_authz_svn.h subversion/include/mod_dav_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_skel.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/repos.c ; else echo "fake" > subversion/mod_dav_svn/repos.lo ; fi + +subversion/mod_dav_svn/util.lo: subversion/mod_dav_svn/util.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/private/svn_string_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/util.c ; else echo "fake" > subversion/mod_dav_svn/util.lo ; fi + +subversion/mod_dav_svn/version.lo: subversion/mod_dav_svn/version.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/version.c ; else echo "fake" > subversion/mod_dav_svn/version.lo ; fi + +subversion/po/de.mo: subversion/po/de.po + +subversion/po/es.mo: subversion/po/es.po + +subversion/po/fr.mo: subversion/po/fr.po + +subversion/po/it.mo: subversion/po/it.po + +subversion/po/ja.mo: subversion/po/ja.po + +subversion/po/ko.mo: subversion/po/ko.po + +subversion/po/nb.mo: subversion/po/nb.po + +subversion/po/pl.mo: subversion/po/pl.po + +subversion/po/pt_BR.mo: subversion/po/pt_BR.po + +subversion/po/sv.mo: subversion/po/sv.po + +subversion/po/zh_CN.mo: subversion/po/zh_CN.po + +subversion/po/zh_TW.mo: subversion/po/zh_TW.po + +subversion/svn/add-cmd.lo: subversion/svn/add-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/blame-cmd.lo: subversion/svn/blame-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/cat-cmd.lo: subversion/svn/cat-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/changelist-cmd.lo: subversion/svn/changelist-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/checkout-cmd.lo: subversion/svn/checkout-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/cl-conflicts.lo: subversion/svn/cl-conflicts.c subversion/include/private/svn_debug.h subversion/include/private/svn_token.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl-conflicts.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/cleanup-cmd.lo: subversion/svn/cleanup-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/commit-cmd.lo: subversion/svn/commit-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/conflict-callbacks.lo: subversion/svn/conflict-callbacks.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl-conflicts.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/copy-cmd.lo: subversion/svn/copy-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/delete-cmd.lo: subversion/svn/delete-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/deprecated.lo: subversion/svn/deprecated.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h + +subversion/svn/diff-cmd.lo: subversion/svn/diff-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/export-cmd.lo: subversion/svn/export-cmd.c subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/file-merge.lo: subversion/svn/file-merge.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_utf_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/help-cmd.lo: subversion/svn/help-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/import-cmd.lo: subversion/svn/import-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/info-cmd.lo: subversion/svn/info-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl-conflicts.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/list-cmd.lo: subversion/svn/list-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/lock-cmd.lo: subversion/svn/lock-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/log-cmd.lo: subversion/svn/log-cmd.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/merge-cmd.lo: subversion/svn/merge-cmd.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/mergeinfo-cmd.lo: subversion/svn/mergeinfo-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/mkdir-cmd.lo: subversion/svn/mkdir-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/move-cmd.lo: subversion/svn/move-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/notify.lo: subversion/svn/notify.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/patch-cmd.lo: subversion/svn/patch-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/propdel-cmd.lo: subversion/svn/propdel-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/propedit-cmd.lo: subversion/svn/propedit-cmd.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/propget-cmd.lo: subversion/svn/propget-cmd.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/proplist-cmd.lo: subversion/svn/proplist-cmd.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/props.lo: subversion/svn/props.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/propset-cmd.lo: subversion/svn/propset-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/relocate-cmd.lo: subversion/svn/relocate-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/resolve-cmd.lo: subversion/svn/resolve-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/resolved-cmd.lo: subversion/svn/resolved-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/revert-cmd.lo: subversion/svn/revert-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/status-cmd.lo: subversion/svn/status-cmd.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/status.lo: subversion/svn/status.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl-conflicts.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/svn.lo: subversion/svn/svn.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/switch-cmd.lo: subversion/svn/switch-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/unlock-cmd.lo: subversion/svn/unlock-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/update-cmd.lo: subversion/svn/update-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/upgrade-cmd.lo: subversion/svn/upgrade-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svn/util.lo: subversion/svn/util.c subversion/include/private/svn_client_private.h subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_token.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h + +subversion/svnadmin/svnadmin.lo: subversion/svnadmin/svnadmin.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/svn_private_config.h + +subversion/svndumpfilter/svndumpfilter.lo: subversion/svndumpfilter/svndumpfilter.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/svn_private_config.h + +subversion/svnlook/svnlook.lo: subversion/svnlook/svnlook.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_private.h subversion/include/private/svn_fspath.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/svn_private_config.h + +subversion/svnmucc/svnmucc.lo: subversion/svnmucc/svnmucc.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h + +subversion/svnrdump/dump_editor.lo: subversion/svnrdump/dump_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/svnrdump/svnrdump.h + +subversion/svnrdump/load_editor.lo: subversion/svnrdump/load_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_repos_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/svnrdump/svnrdump.h + +subversion/svnrdump/svnrdump.lo: subversion/svnrdump/svnrdump.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn_private_config.h subversion/svnrdump/svnrdump.h + +subversion/svnrdump/util.lo: subversion/svnrdump/util.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/svnrdump/svnrdump.h + +subversion/svnserve/cyrus_auth.lo: subversion/svnserve/cyrus_auth.c subversion/include/private/ra_svn_sasl.h subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/svnserve/server.h + +subversion/svnserve/log-escape.lo: subversion/svnserve/log-escape.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svnserve/server.h + +subversion/svnserve/serve.lo: subversion/svnserve/serve.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/svn_private_config.h subversion/svnserve/server.h + +subversion/svnserve/svnserve.lo: subversion/svnserve/svnserve.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_auth.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/svn_private_config.h subversion/svnserve/server.h subversion/svnserve/winservice.h + +subversion/svnserve/winservice.lo: subversion/svnserve/winservice.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/svnserve/winservice.h + +subversion/svnsync/svnsync.lo: subversion/svnsync/svnsync.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/svn_private_config.h subversion/svnsync/sync.h + +subversion/svnsync/sync.lo: subversion/svnsync/sync.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h subversion/svnsync/sync.h + +subversion/svnversion/svnversion.lo: subversion/svnversion/svnversion.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h + +subversion/tests/cmdline/atomic-ra-revprop-change.lo: subversion/tests/cmdline/atomic-ra-revprop-change.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h + +subversion/tests/cmdline/entries-dump.lo: subversion/tests/cmdline/entries-dump.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/tests/libsvn_client/client-test.lo: subversion/tests/libsvn_client/client-test.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_delta/random-test.lo: subversion/tests/libsvn_delta/random-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/compose_delta.c subversion/libsvn_delta/delta.h subversion/tests/libsvn_delta/delta-window-test.h subversion/tests/libsvn_delta/range-index-test.h subversion/tests/svn_test.h + +subversion/tests/libsvn_delta/svndiff-test.lo: subversion/tests/libsvn_delta/svndiff-test.c subversion/include/private/svn_debug.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_quoprint.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_delta/vdelta-test.lo: subversion/tests/libsvn_delta/vdelta-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h subversion/tests/libsvn_delta/delta-window-test.h subversion/tests/svn_test.h + +subversion/tests/libsvn_delta/window-test.lo: subversion/tests/libsvn_delta/window-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_diff/diff-diff3-test.lo: subversion/tests/libsvn_diff/diff-diff3-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/tests/svn_test.h + +subversion/tests/libsvn_diff/parse-diff-test.lo: subversion/tests/libsvn_diff/parse-diff-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/tests/svn_test.h + +subversion/tests/libsvn_fs/fs-test.lo: subversion/tests/libsvn_fs/fs-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_delta/delta.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_fs/locks-test.lo: subversion/tests/libsvn_fs/locks-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_fs_base/changes-test.lo: subversion/tests/libsvn_fs_base/changes-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_fs_base/fs-base-test.lo: subversion/tests/libsvn_fs_base/fs-base-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_fs_base/strings-reps-test.lo: subversion/tests/libsvn_fs_base/strings-reps-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_fs_fs/fs-pack-test.lo: subversion/tests/libsvn_fs_fs/fs-pack-test.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_string_private.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_fs/fs.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_ra/ra-test.lo: subversion/tests/libsvn_ra/ra-test.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_local/ra_local.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_ra_local/ra-local-test.lo: subversion/tests/libsvn_ra_local/ra-local-test.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_ra_local/ra_local.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_repos/dir-delta-editor.lo: subversion/tests/libsvn_repos/dir-delta-editor.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/libsvn_repos/dir-delta-editor.h subversion/tests/svn_test.h + +subversion/tests/libsvn_repos/repos-test.lo: subversion/tests/libsvn_repos/repos-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/tests/libsvn_repos/dir-delta-editor.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_subr/auth-test.lo: subversion/tests/libsvn_subr/auth-test.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/cache-test.lo: subversion/tests/libsvn_subr/cache-test.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/checksum-test.lo: subversion/tests/libsvn_subr/checksum-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_pseudo_md5.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/compat-test.lo: subversion/tests/libsvn_subr/compat-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/svn_private_config.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/config-test.lo: subversion/tests/libsvn_subr/config-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/crypto-test.lo: subversion/tests/libsvn_subr/crypto-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/crypto.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/dirent_uri-test.lo: subversion/tests/libsvn_subr/dirent_uri-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/error-code-test.lo: subversion/tests/libsvn_subr/error-code-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/error-test.lo: subversion/tests/libsvn_subr/error-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_error_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/hashdump-test.lo: subversion/tests/libsvn_subr/hashdump-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/io-test.lo: subversion/tests/libsvn_subr/io-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_subr/mergeinfo-test.lo: subversion/tests/libsvn_subr/mergeinfo-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/named_atomic-test-proc.lo: subversion/tests/libsvn_subr/named_atomic-test-proc.c subversion/include/private/svn_debug.h subversion/include/private/svn_named_atomic.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/libsvn_subr/named_atomic-test-common.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/named_atomic-test.lo: subversion/tests/libsvn_subr/named_atomic-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_named_atomic.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/libsvn_subr/named_atomic-test-common.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/opt-test.lo: subversion/tests/libsvn_subr/opt-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/path-test.lo: subversion/tests/libsvn_subr/path-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/revision-test.lo: subversion/tests/libsvn_subr/revision-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/skel-test.lo: subversion/tests/libsvn_subr/skel-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_subr/spillbuf-test.lo: subversion/tests/libsvn_subr/spillbuf-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/stream-test.lo: subversion/tests/libsvn_subr/stream-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_io_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/string-test.lo: subversion/tests/libsvn_subr/string-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/subst_translate-test.lo: subversion/tests/libsvn_subr/subst_translate-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/time-test.lo: subversion/tests/libsvn_subr/time-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/translate-test.lo: subversion/tests/libsvn_subr/translate-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/tests/svn_test.h + +subversion/tests/libsvn_subr/utf-test.lo: subversion/tests/libsvn_subr/utf-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/tests/svn_test.h + +subversion/tests/libsvn_wc/conflict-data-test.lo: subversion/tests/libsvn_wc/conflict-data-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h + +subversion/tests/libsvn_wc/db-test.lo: subversion/tests/libsvn_wc/db-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h + +subversion/tests/libsvn_wc/entries-compat.lo: subversion/tests/libsvn_wc/entries-compat.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h + +subversion/tests/libsvn_wc/op-depth-test.lo: subversion/tests/libsvn_wc/op-depth-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h + +subversion/tests/libsvn_wc/pristine-store-test.lo: subversion/tests/libsvn_wc/pristine-store-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h + +subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/libsvn_wc/wc-incomplete-tester.lo: subversion/tests/libsvn_wc/wc-incomplete-tester.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/tests/libsvn_wc/wc-lock-tester.lo: subversion/tests/libsvn_wc/wc-lock-tester.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h + +subversion/tests/libsvn_wc/wc-queries-test.lo: subversion/tests/libsvn_wc/wc-queries-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_wc/wc-queries.h subversion/svn_private_config.h subversion/tests/svn_test.h + +subversion/tests/libsvn_wc/wc-test.lo: subversion/tests/libsvn_wc/wc-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h + +subversion/tests/svn_test_fs.lo: subversion/tests/svn_test_fs.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h + +subversion/tests/svn_test_main.lo: subversion/tests/svn_test_main.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/tests/svn_test.h + +tools/client-side/svn-bench/help-cmd.lo: tools/client-side/svn-bench/help-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h + +tools/client-side/svn-bench/notify.lo: tools/client-side/svn-bench/notify.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h + +tools/client-side/svn-bench/null-export-cmd.lo: tools/client-side/svn-bench/null-export-cmd.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h + +tools/client-side/svn-bench/null-list-cmd.lo: tools/client-side/svn-bench/null-list-cmd.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h + +tools/client-side/svn-bench/null-log-cmd.lo: tools/client-side/svn-bench/null-log-cmd.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h + +tools/client-side/svn-bench/svn-bench.lo: tools/client-side/svn-bench/svn-bench.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h + +tools/client-side/svn-bench/util.lo: tools/client-side/svn-bench/util.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h + +tools/dev/fsfs-access-map.lo: tools/dev/fsfs-access-map.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +tools/dev/fsfs-reorg.lo: tools/dev/fsfs-reorg.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h + +tools/dev/svnraisetreeconflict/svnraisetreeconflict.lo: tools/dev/svnraisetreeconflict/svnraisetreeconflict.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h + +tools/diff/diff.lo: tools/diff/diff.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h + +tools/diff/diff3.lo: tools/diff/diff3.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +tools/diff/diff4.lo: tools/diff/diff4.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h + +tools/server-side/fsfs-stats.lo: tools/server-side/fsfs-stats.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h + +tools/server-side/mod_dontdothat/mod_dontdothat.lo: tools/server-side/mod_dontdothat/mod_dontdothat.c subversion/include/mod_dav_svn.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h + if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)tools/server-side/mod_dontdothat/mod_dontdothat.c ; else echo "fake" > tools/server-side/mod_dontdothat/mod_dontdothat.lo ; fi + +tools/server-side/svn-populate-node-origins-index.lo: tools/server-side/svn-populate-node-origins-index.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h + +tools/server-side/svn-rep-sharing-stats.lo: tools/server-side/svn-rep-sharing-stats.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/svn_private_config.h + +tools/server-side/svnauthz.lo: tools/server-side/svnauthz.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h + +tools/server-side/svnauthz.lo: tools/server-side/svnauthz.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h + diff --git a/build.conf b/build.conf new file mode 100644 index 000000000000..fd620146f322 --- /dev/null +++ b/build.conf @@ -0,0 +1,1377 @@ +# +# build.conf -- configuration information for building Subversion +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# +# This file is processed by gen-make.py, creating build-outputs.mk +# + +# ---------------------------------------------------------------------------- +# +# PREDEFINED SECTION +# +# This [options] section is global in scope, providing information to the +# process, rather than defining a build target, as all other sections do. +# + +[options] +includes = subversion/include/*.h +include-wildcards = *.h *.i *.swg +private-includes = + subversion/include/private/*.h + subversion/bindings/swig/include/*.swg + subversion/libsvn_delta/compose_delta.c + subversion/bindings/cxxhl/include/*.hpp + subversion/bindings/cxxhl/include/svncxxhl/*.hpp +private-built-includes = + subversion/svn_private_config.h + subversion/libsvn_fs_fs/rep-cache-db.h + subversion/libsvn_wc/wc-metadata.h + subversion/libsvn_wc/wc-queries.h + subversion/libsvn_wc/wc-checks.h + subversion/libsvn_subr/internal_statements.h + subversion/bindings/swig/proxy/swig_python_external_runtime.swg + subversion/bindings/swig/proxy/swig_perl_external_runtime.swg + subversion/bindings/swig/proxy/swig_ruby_external_runtime.swg + subversion/bindings/swig/proxy/rubyhead.swg + subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItemStateFlags.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_NativeResources.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_Path.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNRepos.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNClient.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Version.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LinkedLib.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LinkedLibIterator.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LoadedLib.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LoadedLibIterator.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Revision.h + subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_UserPasswordCallback.h + + +test-scripts = + subversion/tests/cmdline/*_tests.py + +bdb-test-scripts = + +swig-python-opts = $(CPPFLAGS) -python -classic +swig-perl-opts = $(CPPFLAGS) -perl -nopm -noproxy +swig-ruby-opts = $(CPPFLAGS) -ruby +swig-languages = python perl ruby +swig-dirs = + subversion/bindings/swig/python + subversion/bindings/swig/perl + subversion/bindings/swig/ruby + subversion/bindings/swig/proxy + +swig-proxy-dir = subversion/bindings/swig/proxy +swig-checkout-files = common.swg swigrun.swg runtime.swg + ruby/rubydef.swg ruby/rubyhead.swg ruby/rubytracking.swg + perl5/perlrun.swg python/pyrun.swg python/python.swg + +# ---------------------------------------------------------------------------- +# +# BUILD TARGETS +# +# Target parameters: +# description - optional build target description +# type - the target type, defines how to build it +# when - the name of an autoconf-substed variable that muset be +# defined to either "true" or "false", that determines +# whether this target should be built and installed. +# path - relative path to target sources +# sources - explicit list of target sources +# install - the installation group/type +# manpages - the man pages associated with this target +# libs - libraries that this target depends on +# nonlibs - dependencies that are not linked into the target +# lang - bindings for language $(lang) +# msvc-libs - additional libraries to link with on Windows +# msvc-export - additional list of files to expose in dsp/vc(x)proj +# msvc-static - visual studio target produces only a static lib +# add-deps - expands to additional autoconf-defined dependencies +# add-install-deps - like add-deps, but for the install step +# external-lib - expands to additional autoconf-defined libs +# external-project - visual studio project to depend on +# + +# The subversion command-line client +[svn] +description = Subversion Client +type = exe +path = subversion/svn +libs = libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_diff libsvn_subr + apriconv apr +manpages = subversion/svn/svn.1 +install = bin + +# The subversion repository administration tool +[svnadmin] +description = Subversion Repository Administrator +type = exe +path = subversion/svnadmin +install = bin +manpages = subversion/svnadmin/svnadmin.1 +libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr + +# The subversion repository dump filtering tool +[svndumpfilter] +description = Subversion Dumpfile Filter +type = exe +path = subversion/svndumpfilter +install = bin +manpages = subversion/svndumpfilter/svndumpfilter.1 +libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr + +# The subversion repository inspection tool +[svnlook] +description = Subversion Repository Browser +type = exe +path = subversion/svnlook +install = bin +manpages = subversion/svnlook/svnlook.1 +libs = libsvn_repos libsvn_fs libsvn_delta libsvn_diff libsvn_subr apriconv apr + +[svnserve] +description = Subversion Server +type = exe +path = subversion/svnserve +install = bin +manpages = subversion/svnserve/svnserve.8 subversion/svnserve/svnserve.conf.5 +libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr libsvn_ra_svn + apriconv apr sasl +msvc-libs = advapi32.lib ws2_32.lib + +[svnsync] +description = Subversion repository replicator +type = exe +path = subversion/svnsync +libs = libsvn_ra libsvn_delta libsvn_subr apr +install = bin +manpages = subversion/svnsync/svnsync.1 + +[svnversion] +description = Subversion Revision Extractor +type = exe +path = subversion/svnversion +libs = libsvn_wc libsvn_subr apriconv apr +install = bin +manpages = subversion/svnversion/svnversion.1 + +[svnrdump] +description = Subversion remote repository dumper and loader +type = exe +path = subversion/svnrdump +libs = libsvn_client libsvn_ra libsvn_repos libsvn_delta libsvn_subr aprutil apr +install = bin +manpages = subversion/svnrdump/svnrdump.1 + +[svnmucc] +description = Subversion Multiple URL Command Client +type = exe +path = subversion/svnmucc +libs = libsvn_client libsvn_ra libsvn_subr libsvn_delta apriconv apr +install = bin +manpages = subversion/svnmucc/svnmucc.1 + +# Support for GNOME Keyring +[libsvn_auth_gnome_keyring] +description = Subversion GNOME Keyring Library +type = lib +install = gnome-keyring-lib +path = subversion/libsvn_auth_gnome_keyring +libs = libsvn_subr apr gnome-keyring + +# Support for KWallet +[libsvn_auth_kwallet] +description = Subversion KWallet Library +type = lib +install = kwallet-lib +path = subversion/libsvn_auth_kwallet +libs = libsvn_subr apr kwallet +link-cmd = $(LINK_CXX_LIB) + +# Library needed by all subversion clients +[libsvn_client] +description = Subversion Client Library +type = lib +path = subversion/libsvn_client +libs = libsvn_wc libsvn_ra libsvn_delta libsvn_diff libsvn_subr apriconv apr +install = lib +msvc-export = svn_client.h private/svn_client_private.h + +# Routines for binary diffing and tree-deltas +[libsvn_delta] +description = Subversion Delta Library +type = lib +install = fsmod-lib +path = subversion/libsvn_delta +libs = libsvn_subr aprutil apriconv apr zlib +msvc-export = svn_delta.h private/svn_editor.h private/svn_delta_private.h + +# Routines for diffing +[libsvn_diff] +description = Subversion Diff Library +type = lib +path = subversion/libsvn_diff +libs = libsvn_subr apriconv apr zlib +install = lib +msvc-export = svn_diff.h private/svn_diff_private.h private/svn_diff_tree.h + +# The repository filesystem library +[libsvn_fs] +description = Subversion Repository Filesystem Library +type = lib +path = subversion/libsvn_fs +install = ramod-lib +libs = libsvn_fs_util libsvn_delta libsvn_subr fs-libs aprutil apr +# conditionally add more dependencies +add-deps = $(SVN_FS_LIB_DEPS) +add-install-deps = $(SVN_FS_LIB_INSTALL_DEPS) +msvc-export = svn_fs.h private/svn_fs_private.h + +[libsvn_fs_base] +type = fs-module +path = subversion/libsvn_fs_base +sources = *.c bdb/*.c util/*.c +install = bdb-lib +libs = libsvn_delta libsvn_subr aprutil apriconv apr bdb libsvn_fs_util +msvc-static = yes + +[libsvn_fs_fs] +type = fs-module +path = subversion/libsvn_fs_fs +install = fsmod-lib +libs = libsvn_delta libsvn_subr aprutil apriconv apr libsvn_fs_util +msvc-static = yes + +# Low-level grab bag of utilities +[libsvn_fs_util] +type = lib +install = fsmod-lib +path = subversion/libsvn_fs_util +libs = libsvn_subr aprutil apriconv apr +msvc-libs = advapi32.lib shfolder.lib +msvc-static = yes + +# General API for accessing repositories +[libsvn_ra] +description = Subversion Repository Access Library +type = lib +path = subversion/libsvn_ra +libs = libsvn_delta libsvn_subr ra-libs apriconv apr +# conditionally add more dependencies +add-deps = $(SVN_RA_LIB_DEPS) +add-install-deps = $(SVN_RA_LIB_INSTALL_DEPS) +install = lib +msvc-export = svn_ra.h private\svn_ra_private.h + +# Accessing repositories via DAV through serf +[libsvn_ra_serf] +type = ra-module +path = subversion/libsvn_ra_serf +install = serf-lib +libs = libsvn_delta libsvn_subr aprutil apriconv apr serf xml +msvc-libs = secur32.lib +msvc-static = yes + +# Accessing repositories via SVN +[libsvn_ra_svn] +type = ra-module +path = subversion/libsvn_ra_svn +install = ramod-lib +libs = libsvn_delta libsvn_subr aprutil apriconv apr sasl +msvc-static = yes + +# Accessing repositories via direct libsvn_fs +[libsvn_ra_local] +type = ra-module +path = subversion/libsvn_ra_local +install = ramod-lib +libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr +msvc-static = yes + +# Routines built on top of libsvn_fs +[libsvn_repos] +description = Subversion Repository Library +type = lib +path = subversion/libsvn_repos +install = ramod-lib +libs = libsvn_fs libsvn_delta libsvn_subr apriconv apr +msvc-export = svn_repos.h private/svn_repos_private.h + +# Low-level grab bag of utilities +[libsvn_subr] +description = Subversion General Utility Library +type = lib +install = fsmod-lib +path = subversion/libsvn_subr +libs = aprutil apriconv apr xml zlib apr_memcache sqlite magic +msvc-libs = kernel32.lib advapi32.lib shfolder.lib ole32.lib + crypt32.lib version.lib psapi.lib +msvc-export = + svn_auth.h svn_base64.h svn_cache_config.h svn_checksum.h svn_cmdline.h + svn_compat.h svn_config.h svn_ctype.h svn_dirent_uri.h svn_dso.h + svn_error.h svn_hash.h svn_io.h svn_iter.h svn_md5.h svn_mergeinfo.h + svn_nls.h svn_opt.h svn_path.h svn_pools.h svn_props.h svn_quoprint.h + svn_sorts.h svn_string.h svn_subst.h svn_time.h svn_types.h svn_user.h + svn_utf.h svn_version.h svn_xml.h + private\svn_atomic.h private\svn_cache.h private\svn_cmdline_private.h + private\svn_debug.h private\svn_error_private.h private\svn_fspath.h + private\svn_log.h private\svn_mergeinfo_private.h + private\svn_opt_private.h private\svn_skel.h private\svn_sqlite.h + private\svn_utf_private.h private\svn_eol_private.h + private\svn_token.h private\svn_adler32.h + private\svn_temp_serializer.h private\svn_io_private.h + private\svn_string_private.h private\svn_magic.h + private\svn_subr_private.h private\svn_mutex.h private\svn_named_atomic.h + +# Working copy management lib +[libsvn_wc] +description = Subversion Working Copy Library +type = lib +path = subversion/libsvn_wc +libs = libsvn_delta libsvn_diff libsvn_subr aprutil apriconv apr +install = lib +msvc-export = svn_wc.h private\svn_wc_private.h + +# Subversion plugin for Apache's mod_dav +[mod_dav_svn] +description = Subversion plug-in for the Apache DAV module +when = INSTALL_APACHE_MODS +type = apache-mod +path = subversion/mod_dav_svn +sources = *.c reports/*.c posts/*.c +libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr +nonlibs = apr aprutil +install = apache-mod +msvc-libs = mod_dav.lib libhttpd.lib + +[mod_authz_svn] +description = Subversion path-based authorization module for Apache +when = INSTALL_APACHE_MODS +type = apache-mod +path = subversion/mod_authz_svn +nonlibs = mod_dav_svn apr aprutil +libs = libsvn_repos libsvn_subr +install = apache-mod +msvc-libs = libhttpd.lib + +[mod_dontdothat] +description = Apache Httpd module to block certain kinds of Apache Subversion requests +when = INSTALL_APACHE_MODS +type = apache-mod +path = tools/server-side/mod_dontdothat +nonlibs = mod_dav_svn apr aprutil +libs = libsvn_subr xml +install = tools +msvc-libs = libhttpd.lib + +# ---------------------------------------------------------------------------- +# +# CONSTRUCTED HEADERS +# + +[rep_cache] +description = Schema for the rep-sharing feature +type = sql-header +path = subversion/libsvn_fs_fs +sources = rep-cache-db.sql + +[wc_queries] +desription = Queries on the WC database +type = sql-header +path = subversion/libsvn_wc +sources = wc-queries.sql + +[subr_sqlite] +description = Internal statements for SQLite interface +type = sql-header +path = subversion/libsvn_subr +sources = internal_statements.sql + + +# ---------------------------------------------------------------------------- +# +# TARGETS FOR I18N SUPPORT +# +[locale] +type = i18n +path = subversion/po +install = locale +external-project = svn_locale + +# ---------------------------------------------------------------------------- +# +# TARGETS FOR SWIG SUPPORT +# + +[swig_core] +type = swig +path = subversion/bindings/swig +sources = core.i +libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby + libsvn_diff libsvn_subr apr +description = Subversion core library bindings +include-runtime = yes + +[swig_client] +type = swig +path = subversion/bindings/swig +sources = svn_client.i +libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby + libsvn_client libsvn_subr apr +nonlibs = swig_core +description = Subversion client library bindings + +[swig_delta] +type = swig +path = subversion/bindings/swig +sources = svn_delta.i +libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby + libsvn_delta libsvn_subr apr +nonlibs = swig_core +description = Subversion delta library bindings + +[swig_diff] +type = swig +path = subversion/bindings/swig +sources = svn_diff.i +libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby + libsvn_diff libsvn_subr apr +nonlibs = swig_core +description = Subversion diff library bindings + +[swig_fs] +type = swig +path = subversion/bindings/swig +sources = svn_fs.i +libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby + libsvn_fs libsvn_subr apr +nonlibs = swig_core +description = Subversion FS library bindings + +[swig_ra] +type = swig +path = subversion/bindings/swig +sources = svn_ra.i +libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby + libsvn_ra libsvn_subr apr +nonlibs = swig_core +description = Subversion RA library bindings + +[swig_repos] +type = swig +path = subversion/bindings/swig +sources = svn_repos.i +libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby + libsvn_repos libsvn_subr apr +nonlibs = swig_core +description = Subversion repository library bindings + +[swig_wc] +type = swig +path = subversion/bindings/swig +sources = svn_wc.i +libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby + libsvn_wc libsvn_subr apr +nonlibs = swig_core +description = Subversion WC library bindings + +# SWIG utility library for Python modules +[libsvn_swig_py] +type = swig_lib +lang = python +path = subversion/bindings/swig/python/libsvn_swig_py +libs = libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_subr apriconv apr +link-cmd = $(LINK) +install = swig-py-lib +# need special build rule to include -DSWIGPYTHON +compile-cmd = $(COMPILE_SWIG_PY) +msvc-static = no + +# SWIG utility library for Perl modules +[libsvn_swig_perl] +type = swig_lib +lang = perl +path = subversion/bindings/swig/perl/libsvn_swig_perl +libs = libsvn_delta libsvn_subr apriconv apr +install = swig-pl-lib +# need special build rule to include +compile-cmd = $(COMPILE_SWIG_PL) +msvc-static = yes + +# SWIG utility library for Ruby modules +[libsvn_swig_ruby] +type = swig_lib +lang = ruby +path = subversion/bindings/swig/ruby/libsvn_swig_ruby +libs = libsvn_client libsvn_wc libsvn_delta libsvn_subr apriconv apr +link-cmd = $(LINK) $(SWIG_RB_LIBS) +install = swig-rb-lib +# need special build rule to include +compile-cmd = $(COMPILE_SWIG_RB) +msvc-static = no + +# ---------------------------------------------------------------------------- +# +# JavaHL targets +# +[javahl-java] +type = java +path = subversion/bindings/javahl/src/org/apache/subversion/javahl + subversion/bindings/javahl/src/org/apache/subversion/javahl/callback + subversion/bindings/javahl/src/org/apache/subversion/javahl/types +src-root = subversion/bindings/javahl/src +sources = *.java +install = javahl-java +link-cmd = $(COMPILE_JAVAHL_JAVAC) +classes = subversion/bindings/javahl/classes +package-roots = org + +[javahl-compat-java] +type = java +path = subversion/bindings/javahl/src/org/tigris/subversion/javahl +sources = *.java +install = javahl-java +link-cmd = $(COMPILE_JAVAHL_JAVAC) +classes = subversion/bindings/javahl/classes +add-deps = $(javahl_java_DEPS) +### Replace JAR call in INSTALL_EXTRA_JAVAHL_JAVA macro Makefile.in. +#jar = svn-javahl.jar +package-roots = org + +[javahl-tests] +type = java +path = subversion/bindings/javahl/tests/org/apache/subversion/javahl +sources = *.java +install = javahl-java +link-cmd = $(COMPILE_JAVAHL_JAVAC) +classes = subversion/bindings/javahl/classes +package-roots = org +### Java targets don't do up-to-date checks yet. +#add-deps = javahl-java +add-deps = $(javahl_java_DEPS) + +[javahl-compat-tests] +type = java +path = subversion/bindings/javahl/tests/org/tigris/subversion/javahl +sources = *.java +install = javahl-java +link-cmd = $(COMPILE_JAVAHL_JAVAC) +classes = subversion/bindings/javahl/classes +package-roots = org +### Java targets don't do up-to-date checks yet. +#add-deps = javahl-compat-java +add-deps = $(javahl_compat_java_DEPS) + +[javahl-types-javah] +type = javah +path = subversion/bindings/javahl/src/org/apache/subversion/javahl/types +classes = subversion/bindings/javahl/classes +headers = subversion/bindings/javahl/include +package = org.apache.subversion.javahl.types +sources = *.java +add-deps = $(javahl_java_DEPS) +install = javahl-javah +link-cmd = $(COMPILE_JAVAHL_JAVAH) -force + +[javahl-callback-javah] +type = javah +path = subversion/bindings/javahl/src/org/apache/subversion/javahl/callback +classes = subversion/bindings/javahl/classes +headers = subversion/bindings/javahl/include +package = org.apache.subversion.javahl.callback +sources = *.java +add-deps = $(javahl_java_DEPS) +install = javahl-javah +link-cmd = $(COMPILE_JAVAHL_JAVAH) -force + +[javahl-javah] +type = javah +path = subversion/bindings/javahl/src/org/apache/subversion/javahl +classes = subversion/bindings/javahl/classes +headers = subversion/bindings/javahl/include +package = org.apache.subversion.javahl +sources = *.java +add-deps = $(javahl_java_DEPS) +install = javahl-javah +link-cmd = $(COMPILE_JAVAHL_JAVAH) -force + +[libsvnjavahl] +description = Subversion Java HighLevel binding +type = lib +path = subversion/bindings/javahl/native +libs = libsvn_repos libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_diff + libsvn_subr libsvn_fs aprutil apriconv apr +sources = *.cpp *.c +add-deps = $(javahl_javah_DEPS) $(javahl_java_DEPS) $(javahl_callback_javah_DEPS) $(javahl_types_javah_DEPS) +install = javahl-lib +# need special build rule to include -I$(JDK)/include/jni.h +compile-cmd = $(COMPILE_JAVAHL_CXX) +link-cmd = $(LINK_JAVAHL_CXX) + +# ---------------------------------------------------------------------------- +# +# C++HL targets +# + +[libsvncxxhl] +description = Subversion C++ HighLevel bindings +type = lib +path = subversion/bindings/cxxhl +libs = libsvn_repos libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_diff + libsvn_subr libsvn_fs aprutil apriconv apr +sources = src/*.cpp +install = cxxhl-lib +msvc-static = yes +compile-cmd = $(COMPILE_CXXHL_CXX) +link-cmd = $(LINK_CXX_LIB) + +[cxxhl-tests] +description = Unit tests for Subversion C++ HighLevel bindings +type = exe +path = subversion/bindings/cxxhl +libs = libsvncxxhl libsvn_subr +sources = tests/*.cpp +install = tests +compile-cmd = $(COMPILE_CXXHL_CXX) +link-cmd = $(LINK_CXX) + +# ---------------------------------------------------------------------------- +# +# TESTING TARGETS +# + +# general library: our C testing framework +[libsvn_test] +type = lib +path = subversion/tests +install = test +libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr aprutil apriconv apr +msvc-static = yes +undefined-lib-symbols = yes + +# ---------------------------------------------------------------------------- +# Tests for libsvn_fs_base + +[fs-base-test] +description = Tests for *public* fs API (svn_fs.h) +type = exe +path = subversion/tests/libsvn_fs_base +sources = fs-base-test.c +install = bdb-test +libs = libsvn_test libsvn_fs libsvn_fs_base libsvn_delta + libsvn_fs_util libsvn_subr apriconv apr + +[strings-reps-test] +description = Test strings/reps in libsvn_fs_base +type = exe +path = subversion/tests/libsvn_fs_base +sources = strings-reps-test.c +install = bdb-test +libs = libsvn_test libsvn_fs libsvn_fs_base libsvn_delta + libsvn_subr apriconv apr + +[changes-test] +description = Test changes in libsvn_fs_base +type = exe +path = subversion/tests/libsvn_fs_base +sources = changes-test.c +install = bdb-test +libs = libsvn_test libsvn_fs libsvn_fs_base libsvn_delta + libsvn_subr apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_fs_fs +[fs-pack-test] +description = Test fsfs packing in libsvn_fs_fs +type = exe +path = subversion/tests/libsvn_fs_fs +sources = fs-pack-test.c +install = test +libs = libsvn_test libsvn_fs libsvn_fs_fs libsvn_delta + libsvn_subr apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_fs + +[locks-test] +description = Test locks in libsvn_fs +type = exe +path = subversion/tests/libsvn_fs +sources = locks-test.c +install = test +libs = libsvn_test libsvn_fs libsvn_delta libsvn_subr apriconv apr + +[fs-test] +description = Test locks in libsvn_fs +type = exe +path = subversion/tests/libsvn_fs +sources = fs-test.c +install = test +libs = libsvn_test libsvn_fs libsvn_delta + libsvn_subr aprutil apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_repos + +[repos-test] +description = Test delta editor in libsvn_repos +type = exe +path = subversion/tests/libsvn_repos +sources = repos-test.c dir-delta-editor.c +install = test +libs = libsvn_test libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_subr + +[auth-test] +description = Test platform-specific auth provider access +type = exe +path = subversion/tests/libsvn_subr +sources = auth-test.c +install = test +libs = libsvn_test libsvn_subr apr + +[cache-test] +description = Test in-memory cache +type = exe +path = subversion/tests/libsvn_subr +sources = cache-test.c +install = test +libs = libsvn_test libsvn_subr apr + +[checksum-test] +description = Test checksum functions +type = exe +path = subversion/tests/libsvn_subr +sources = checksum-test.c +install = test +libs = libsvn_test libsvn_subr apr + +[compat-test] +description = Test compatibility functions +type = exe +path = subversion/tests/libsvn_subr +sources = compat-test.c +install = test +libs = libsvn_test libsvn_subr apr + +[config-test] +description = Test svn_config utilities +type = exe +path = subversion/tests/libsvn_subr +sources = config-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[crypto-test] +description = Test svn_crypto utilities +type = exe +path = subversion/tests/libsvn_subr +sources = crypto-test.c +install = test +libs = libsvn_test libsvn_subr aprutil apr + +[dirent_uri-test] +description = Test dirent_uri library +type = exe +path = subversion/tests/libsvn_subr +sources = dirent_uri-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[error-test] +description = Test error library +type = exe +path = subversion/tests/libsvn_subr +sources = error-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[error-code-test] +description = Test error library +type = exe +path = subversion/tests/libsvn_subr +sources = error-code-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[hashdump-test] +description = Test hashfile format for props +type = exe +path = subversion/tests/libsvn_subr +sources = hashdump-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[io-test] +description = Test I/O Operations +type = exe +path = subversion/tests/libsvn_subr +sources = io-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[opt-test] +description = Test options library +type = exe +path = subversion/tests/libsvn_subr +sources = opt-test.c +install = test +libs = libsvn_test libsvn_subr apr + +[mergeinfo-test] +description = Test mergeinfo library +type = exe +path = subversion/tests/libsvn_subr +sources = mergeinfo-test.c +install = test +libs = libsvn_test libsvn_subr apr + +[named_atomic-test] +description = Test named atomics +type = exe +path = subversion/tests/libsvn_subr +sources = named_atomic-test.c +install = test +libs = libsvn_test libsvn_subr apr + +[named_atomic-proc-test] +description = Sub-process for named atomics +type = exe +path = subversion/tests/libsvn_subr +sources = named_atomic-test-proc.c +install = sub-test +libs = libsvn_subr apr + +[path-test] +description = Test path library +type = exe +path = subversion/tests/libsvn_subr +sources = path-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[revision-test] +description = Test revision library +type = exe +path = subversion/tests/libsvn_subr +sources = revision-test.c +install = test +libs = libsvn_test libsvn_subr apr + +[skel-test] +description = Test skels in libsvn_subr +type = exe +path = subversion/tests/libsvn_subr +sources = skel-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[spillbuf-test] +description = Test spillbuf in libsvn_subr +type = exe +path = subversion/tests/libsvn_subr +sources = spillbuf-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[stream-test] +description = Test stream library +type = exe +path = subversion/tests/libsvn_subr +sources = stream-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[string-test] +description = Test svn_stringbuf_t utilities +type = exe +path = subversion/tests/libsvn_subr +sources = string-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[time-test] +description = Test time functions +type = exe +path = subversion/tests/libsvn_subr +sources = time-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[utf-test] +description = Test UTF-8 functions +type = exe +path = subversion/tests/libsvn_subr +sources = utf-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[subst_translate-test] +description = Test the svn_subst_translate* functions +type = exe +path = subversion/tests/libsvn_subr +sources = subst_translate-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +[translate-test] +description = Test eol conversion and keyword substitution routines +type = exe +path = subversion/tests/libsvn_subr +sources = translate-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_delta + +[random-test] +description = Use random data to test delta processing +type = exe +path = subversion/tests/libsvn_delta +sources = random-test.c +install = test +libs = libsvn_test libsvn_delta libsvn_subr apriconv apr + +[window-test] +description = Test delta window generation +type = exe +path = subversion/tests/libsvn_delta +sources = window-test.c +install = test +libs = libsvn_test libsvn_delta libsvn_subr apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_client + +[client-test] +description = Test low-level functionality in libsvn_client +type = exe +path = subversion/tests/libsvn_client +sources = client-test.c +install = test +libs = libsvn_test libsvn_client libsvn_wc libsvn_repos libsvn_ra libsvn_fs libsvn_delta libsvn_subr apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_diff + +[diff-diff3-test] +description = Test the diff/diff3 library +type = exe +path = subversion/tests/libsvn_diff +sources = diff-diff3-test.c +install = test +libs = libsvn_test libsvn_diff libsvn_subr apriconv apr + +[parse-diff-test] +description = Test unidiff parsing +type = exe +path = subversion/tests/libsvn_diff +sources = parse-diff-test.c +install = test +libs = libsvn_test libsvn_diff libsvn_subr apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_ra + +[ra-test] +description = Test a few things in libsvn_ra +type = exe +path = subversion/tests/libsvn_ra +sources = ra-test.c +install = test +libs = libsvn_test libsvn_ra libsvn_fs libsvn_delta libsvn_subr + apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_ra_local + +[ra-local-test] +description = Test a few things in libsvn_ra_local +type = exe +path = subversion/tests/libsvn_ra_local +sources = ra-local-test.c +install = test +libs = libsvn_test libsvn_ra_local libsvn_ra libsvn_fs libsvn_delta libsvn_subr + apriconv apr + +# ---------------------------------------------------------------------------- +# Tests for libsvn_wc + +[conflict-data-test] +description = Test the storage of tree conflict data +type = exe +path = subversion/tests/libsvn_wc +sources = conflict-data-test.c utils.c +install = test +libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr + +[db-test] +description = Test the wc-ng database subsystem +type = exe +path = subversion/tests/libsvn_wc +sources = db-test.c utils.c +install = test +libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr + +[pristine-store-test] +description = Test the wc-ng pristine text storage subsystem +type = exe +path = subversion/tests/libsvn_wc +sources = pristine-store-test.c utils.c +install = test +libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr + +[entries-compat-test] +description = Test backwards compat for the entry interface +type = exe +path = subversion/tests/libsvn_wc +sources = entries-compat.c utils.c +install = test +libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr + +[op-depth-test] +description = Test layered tree changes +type = exe +path = subversion/tests/libsvn_wc +sources = op-depth-test.c utils.c +install = test +libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr + +[wc-queries-test] +description = Test Sqlite query evaluation +type = exe +path = subversion/tests/libsvn_wc +sources = wc-queries-test.c +install = test +libs = libsvn_test libsvn_subr apriconv apr sqlite + +[wc-test] +description = Test the main WC API functions +type = exe +path = subversion/tests/libsvn_wc +sources = wc-test.c utils.c +install = test +libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr + +# ---------------------------------------------------------------------------- +# These are not unit tests at all, they are small programs that exercise +# parts of the libsvn_delta API from the command line. They are stuck here +# because of some historical association with the test-suite, but should +# really be put somewhere else. + +# test our textdelta encoding +[svndiff-test] +type = exe +path = subversion/tests/libsvn_delta +sources = svndiff-test.c +install = test +libs = libsvn_delta libsvn_subr apriconv apr +testing = skip + +# compare two files, print txdelta windows +[vdelta-test] +type = exe +path = subversion/tests/libsvn_delta +sources = vdelta-test.c +install = test +libs = libsvn_delta libsvn_subr apriconv apr +testing = skip + +[entries-dump] +type = exe +path = subversion/tests/cmdline +sources = entries-dump.c +install = test +libs = libsvn_wc libsvn_subr apriconv apr +testing = skip + +[atomic-ra-revprop-change] +type = exe +path = subversion/tests/cmdline +sources = atomic-ra-revprop-change.c +install = test +libs = libsvn_ra libsvn_subr apriconv apr +testing = skip + +[wc-lock-tester] +type = exe +path = subversion/tests/libsvn_wc +sources = wc-lock-tester.c +install = test +libs = libsvn_wc libsvn_subr apriconv apr +testing = skip + +[wc-incomplete-tester] +type = exe +path = subversion/tests/libsvn_wc +sources = wc-incomplete-tester.c +install = test +libs = libsvn_wc libsvn_subr apriconv apr +testing = skip + +# ---------------------------------------------------------------------------- +# +# EXTERNAL TARGETS (NO BUILD NEEDED) +# + +[apr] +type = lib +external-lib = $(SVN_APR_LIBS) +msvc-libs = ws2_32.lib rpcrt4.lib mswsock.lib + +[aprutil] +type = lib +external-lib = $(SVN_APRUTIL_LIBS) + +[apriconv] +type = lib +external-lib = $(SVN_APRUTIL_LIBS) + +[bdb] +type = lib +external-lib = $(SVN_DB_LIBS) + +[gnome-keyring] +type = lib +external-lib = $(SVN_GNOME_KEYRING_LIBS) + +[kwallet] +type = lib +external-lib = $(SVN_KWALLET_LIBS) + +[magic] +type = lib +external-lib = $(SVN_MAGIC_LIBS) + +[sasl] +type = lib +external-lib = $(SVN_SASL_LIBS) + +[zlib] +type = lib +external-lib = $(SVN_ZLIB_LIBS) +external-project = zlib +msvc-static = yes + +[apr_memcache] +type = lib +external-lib = $(SVN_APR_MEMCACHE_LIBS) + +[serf] +type = lib +external-lib = $(SVN_SERF_LIBS) +external-project = serf/serf +libs = apr aprutil xml +msvc-static = yes + +[sqlite] +type = lib +external-lib = $(SVN_SQLITE_LIBS) + +[xml] +type = lib +external-lib = $(SVN_XML_LIBS) + +[ra-libs] +type = lib +external-lib = $(SVN_RA_LIB_LINK) +libs = libsvn_ra_serf libsvn_ra_local libsvn_ra_svn + +[fs-libs] +type = lib +external-lib = $(SVN_FS_LIB_LINK) +libs = libsvn_fs_base libsvn_fs_fs + +[__ALL__] +type = project +path = build/win32 +libs = svn svnadmin svndumpfilter svnlook svnmucc svnserve svnrdump svnsync + svnversion + mod_authz_svn mod_dav_svn mod_dontdothat + svnauthz svnauthz-validate svnraisetreeconflict + +[__ALL_TESTS__] +type = project +path = build/win32 +libs = __ALL__ + fs-test fs-base-test fs-fsfs-test fs-pack-test skel-test + strings-reps-test changes-test locks-test repos-test + checksum-test compat-test config-test hashdump-test mergeinfo-test + opt-test path-test stream-test string-test time-test utf-test + error-test error-code-test cache-test spillbuf-test crypto-test + named_atomic-test named_atomic-proc-test revision-test + subst_translate-test io-test + translate-test + random-test window-test + diff-diff3-test + ra-test + ra-local-test + svndiff-test vdelta-test + entries-dump atomic-ra-revprop-change wc-lock-tester wc-incomplete-tester + client-test + conflict-data-test db-test pristine-store-test entries-compat-test + op-depth-test dirent_uri-test wc-queries-test wc-test + auth-test + parse-diff-test + +[__MORE__] +type = project +path = build/win32 +libs = __ALL_TESTS__ + diff diff3 diff4 fsfs-reorg fsfs-stats fsfs-access-map svn-bench + svn-rep-sharing-stats svn-populate-node-origins-index + +[__LIBS__] +type = project +path = build/win32 +libs = fs-libs ra-libs libsvn_client libsvn_subr libsvn_wc + aprutil apriconv apr + +[__CONFIG__] +type = lib +external-project = svn_config + +[__SWIG_PYTHON__] +type = swig_project +path = build/win32 +libs = swig_client swig_delta swig_diff swig_fs swig_ra swig_repos swig_wc swig_core +lang = python + +[__SWIG_PERL__] +type = swig_project +path = build/win32 +libs = swig_client swig_delta swig_fs swig_ra swig_repos swig_wc swig_core +lang = perl + +[__SWIG_RUBY__] +type = swig_project +path = build/win32 +libs = swig_client swig_delta swig_fs swig_ra swig_repos swig_wc swig_core +lang = ruby + +[__JAVAHL__] +type = project +path = build/win32 +libs = javahl-java javahl-javah libsvnjavahl + +[__JAVAHL_TESTS__] +type = project +path = build/win32 +libs = __JAVAHL__ javahl-tests javahl-compat-tests + +# ---------------------------------------------------------------------------- +# Contrib and tools + +[fsfs-reorg] +type = exe +path = tools/dev +sources = fsfs-reorg.c +install = tools +libs = libsvn_delta libsvn_subr apr + +[fsfs-stats] +type = exe +path = tools/server-side +sources = fsfs-stats.c +install = tools +libs = libsvn_delta libsvn_subr apr + +[fsfs-access-map] +type = exe +path = tools/dev +sources = fsfs-access-map.c +install = tools +libs = libsvn_subr apr + +[diff] +type = exe +path = tools/diff +sources = diff.c +install = tools +libs = libsvn_diff libsvn_subr apriconv apr + +[diff3] +type = exe +path = tools/diff +sources = diff3.c +install = tools +libs = libsvn_diff libsvn_subr apriconv apr + +[diff4] +type = exe +path = tools/diff +sources = diff4.c +install = tools +libs = libsvn_diff libsvn_subr apriconv apr + +[svn-bench] +type = exe +path = tools/client-side/svn-bench +install = tools +libs = libsvn_client libsvn_wc libsvn_ra libsvn_subr libsvn_delta + apriconv apr + +[svnauthz] +description = Authz config file tool +type = exe +path = tools/server-side +sources = svnauthz.c +install = tools +libs = libsvn_repos libsvn_fs libsvn_subr apr + +# svnauthz-validate is the compat mode of the new svnauthz tool. It is +# exactly the same code as svnauthz. This duplicated target is needed +# in order to easily test both commands as part of the build since libtool +# does not provide a way to set argv[0] different from the commands actual +# name in the wrapper script. +[svnauthz-validate] +description = Authz config file validator +type = exe +path = tools/server-side +sources = svnauthz.c +install = tools +libs = libsvn_repos libsvn_fs libsvn_subr apr + +[svn-populate-node-origins-index] +type = exe +path = tools/server-side +sources = svn-populate-node-origins-index.c +install = tools +libs = libsvn_repos libsvn_fs libsvn_subr apr + +[svnraisetreeconflict] +description = Tool to Flag a Tree Conflict +type = exe +path = tools/dev/svnraisetreeconflict +libs = libsvn_wc libsvn_subr apriconv apr +install = tools + +[svn-rep-sharing-stats] +type = exe +path = tools/server-side +sources = svn-rep-sharing-stats.c +install = tools +libs = libsvn_repos libsvn_fs libsvn_fs_fs libsvn_subr apriconv apr diff --git a/configure b/configure new file mode 100755 index 000000000000..57dadd66df4d --- /dev/null +++ b/configure @@ -0,0 +1,27324 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69 for subversion 1.8.0. +# +# Report bugs to . +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1 + + test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\ + || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org and +$0: http://subversion.apache.org/ about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + +SHELL=${CONFIG_SHELL-/bin/sh} + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='subversion' +PACKAGE_TARNAME='subversion' +PACKAGE_VERSION='1.8.0' +PACKAGE_STRING='subversion 1.8.0' +PACKAGE_BUGREPORT='http://subversion.apache.org/' +PACKAGE_URL='' + +ac_unique_file="subversion/include/svn_types.h" +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='LTLIBOBJS +SVN_CONFIG_SCRIPT_FILES +INCLUDE_OUTPUTS +JAVAHL_COMPAT_TESTS_TARGET +JAVAHL_TESTS_TARGET +JAVA_CLASSPATH +LT_CXX_LIBADD +FIX_JAVAHL_LIB +JAVAHL_OBJDIR +INSTALL_EXTRA_JAVAHL_LIB +SVN_FS_LIB_LINK +SVN_FS_LIB_INSTALL_DEPS +SVN_FS_LIB_DEPS +SVN_RA_LIB_LINK +SVN_RA_LIB_INSTALL_DEPS +SVN_RA_LIB_DEPS +CTYPESGEN +SWIG_RB_TEST_VERBOSE +SWIG_RB_SITE_ARCH_DIR +SWIG_RB_SITE_LIB_DIR +SWIG_RB_COMPILE +SWIG_RB_INCLUDES +SWIG_RB_LIBS +SWIG_RB_LINK +SWIG_PL_INCLUDES +SWIG_PY_LIBS +SWIG_PY_LINK +SWIG_PY_COMPILE +SWIG_PY_INCLUDES +SWIG +RUBY_TEENY +RUBY_MINOR +RUBY_MAJOR +RDOC +RUBY +PERL +JNI_INCLUDES +JAR +JAVAH +JAVADOC +JAVAC_FLAGS +JAVAC +JAVA +JDK +PYTHON +MOD_ACTIVATION +SVN_ZLIB_LIBS +SVN_ZLIB_INCLUDES +libsvn_wc_LDFLAGS +libsvn_subr_LDFLAGS +libsvn_repos_LDFLAGS +libsvn_ra_svn_LDFLAGS +libsvn_ra_serf_LDFLAGS +libsvn_ra_local_LDFLAGS +libsvn_ra_LDFLAGS +libsvn_fs_util_LDFLAGS +libsvn_fs_fs_LDFLAGS +libsvn_fs_base_LDFLAGS +libsvn_fs_LDFLAGS +libsvn_diff_LDFLAGS +libsvn_delta_LDFLAGS +libsvn_client_LDFLAGS +libsvn_auth_kwallet_LDFLAGS +libsvn_auth_gnome_keyring_LDFLAGS +LIBOBJS +BDB_TEST_PROGRAMS +BDB_TEST_DEPS +INSTALL_RULES +INSTALL_STATIC_RULES +BUILD_RULES +SVN_KWALLET_LIBS +SVN_KWALLET_INCLUDES +KDE4_CONFIG +SVN_MAGIC_LIBS +SVN_MAGIC_INCLUDES +MSGFMTFLAGS +NO_GETTEXT_CODESET +GETTEXT_CODESET +XGETTEXT +MSGMERGE +MSGFMT +SVN_GNOME_KEYRING_LIBS +SVN_GNOME_KEYRING_INCLUDES +SVN_HAVE_GPG_AGENT +SVN_SASL_LIBS +SVN_SASL_INCLUDES +SVN_DB_LIBS +SVN_DB_INCLUDES +SVN_XML_LIBS +SVN_XML_INCLUDES +DOXYGEN +TRANG +LT_NO_UNDEFINED +TRANSFORM_LIBTOOL_SCRIPTS +LT_LDFLAGS +LT_CFLAGS +SVN_LIBTOOL +CXXCPP +OTOOL64 +OTOOL +LIPO +NMEDIT +DSYMUTIL +MANIFEST_TOOL +AWK +RANLIB +STRIP +ac_ct_AR +AR +DLLTOOL +OBJDUMP +NM +ac_ct_DUMPBIN +DUMPBIN +LD +FGREP +LIBTOOL +SVN_BINDIR +SVN_SQLITE_LIBS +SVN_SQLITE_INCLUDES +INSTALL_APACHE_MODS +APACHE_LIBEXECDIR +APACHE_INCLUDES +APACHE_LDFLAGS +APXS +SVN_APR_MEMCACHE_LIBS +SVN_APR_MEMCACHE_INCLUDES +SVN_SERF_LIBS +SVN_SERF_INCLUDES +PKG_CONFIG +SVN_LT_SOVERSION +SVN_APRUTIL_LIBS +SVN_APRUTIL_CONFIG +SVN_APRUTIL_INCLUDES +SVN_APR_SHLIB_PATH_VAR +SVN_APR_LIBS +SVN_APR_INCLUDES +SVN_APR_CONFIG +MKDIR +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +LN_S +EGREP +GREP +target_os +target_vendor +target_cpu +target +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +SED +CPP +CXXMAINTAINERFLAGS +CXXMODEFLAGS +ac_ct_CXX +CXXFLAGS +CXX +CMAINTAINERFLAGS +CMODEFLAGS +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +SWIG_LDFLAGS +canonicalized_srcdir +abs_builddir +abs_srcdir +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +with_apr +with_apr_util +with_serf +with_apr_memcache +with_apxs +with_apache_libexecdir +with_sqlite +enable_sqlite_compatibility_version +enable_shared +enable_static +with_pic +enable_fast_install +with_gnu_ld +with_sysroot +enable_libtool_lock +enable_experimental_libtool +enable_all_static +enable_local_library_preloading +with_trang +with_doxygen +with_expat +with_berkeley_db +with_sasl +enable_keychain +with_gpg_agent +with_gnome_keyring +enable_ev2_impl +enable_nls +with_libmagic +with_kwallet +enable_plaintext_password_storage +with_openssl +enable_debug +enable_optimize +enable_disallowing_of_undefined_references +enable_maintainer_mode +enable_full_version_match +with_editor +with_zlib +enable_mod_activation +enable_gcov +enable_gprof +with_jdk +with_jikes +with_swig +with_ruby_sitedir +with_ruby_test_verbose +with_ctypesgen +enable_runtime_module_search +enable_javahl +with_junit +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CXX +CXXFLAGS +CCC +CPP +CXXCPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures subversion 1.8.0 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/subversion] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] + --target=TARGET configure for building compilers for TARGET [HOST] +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of subversion 1.8.0:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-sqlite-compatibility-version=X.Y.Z + Allow binary to run against SQLite as old as ARG + --enable-shared[=PKGS] build shared libraries [default=yes] + --enable-static[=PKGS] build static libraries [default=yes] + --enable-fast-install[=PKGS] + optimize for fast installation [default=yes] + --disable-libtool-lock avoid locking (might break parallel builds) + --enable-experimental-libtool + Use APR's libtool + --enable-static Build static libraries + --enable-shared Build shared libraries + --enable-all-static Build completely static (standalone) binaries. + --enable-local-library-preloading + Enable preloading of locally built libraries in + locally built executables. This may be necessary for + testing prior to installation on some platforms. It + does not work on some platforms (Darwin, OpenBSD, + ...). + --disable-keychain Disable use of Mac OS KeyChain for auth credentials + --enable-ev2-impl Use Ev2 implementations, where available + [EXPERIMENTAL] + --disable-nls Disable gettext functionality + --disable-plaintext-password-storage + Disable on-disk caching of plaintext passwords and + passphrases. (Leaving this functionality enabled + will not force Subversion to store passwords in + plaintext, but does permit users to explicitly allow + that behavior via runtime configuration.) + --enable-debug Turn on debugging + --enable-optimize Turn on optimizations + --enable-disallowing-of-undefined-references + Use -Wl,--no-undefined flag during linking of some + libraries to disallow undefined references + --enable-maintainer-mode + Turn on debugging and very strict compile-time + warnings + --disable-full-version-match + Disable the full version match rules when checking + Subversion library compatibility. + --enable-mod-activation Enable mod_dav_svn in httpd.conf + --enable-gcov Turn on gcov coverage testing (GCC only). + --enable-gprof Produce gprof profiling data in 'gmon.out' (GCC + only). + --enable-runtime-module-search + Turn on dynamic loading of RA/FS libraries including + third-party FS libraries + --enable-javahl Enable compilation of Java high-level bindings + (requires C++) + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-apr=PATH prefix for installed APR, path to APR build tree, + or the full path to apr-config + --with-apr-util=PATH prefix for installed APU, path to APU build tree, + or the full path to apu-config + --with-serf=PREFIX Serf HTTP client library (enabled by default if + found) + --with-apr_memcache=PREFIX + Standalone apr_memcache client library + --with-apxs[=FILE] Build shared Apache modules. FILE is the optional + pathname to the Apache apxs tool; defaults to + "apxs". + --with-apache-libexecdir[=PATH] + Install Apache modules to Apache's configured + modules directory instead of LIBEXECDIR; if PATH is + given, install to PATH. + --with-sqlite=PREFIX Use installed SQLite library or amalgamation file. + --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use + both] + --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-sysroot=DIR Search for dependent libraries within DIR + (or the compiler's sysroot if not specified). + --with-trang=PATH Specify the command to run the trang schema + converter + --with-doxygen=PATH Specify the command to run doxygen + --with-expat=INCLUDES:LIB_SEARCH_DIRS:LIBS + Specify location of Expat + --with-berkeley-db[=HEADER:INCLUDES:LIB_SEARCH_DIRS:LIBS] + The Subversion Berkeley DB based filesystem library + requires Berkeley DB $db_version or newer. If you + specify `--without-berkeley-db', that library will + not be built. If you omit the argument of this + option completely, the configure script will use + Berkeley DB used by APR-UTIL. + --with-sasl=PATH Compile with libsasl2 in PATH + --without-gpg-agent Disable support for GPG-Agent + --with-gnome-keyring Enable use of GNOME Keyring for auth credentials + (enabled by default if found) + --with-libmagic=PREFIX libmagic filetype detection library + --with-kwallet[=PATH] Enable use of KWallet (KDE 4) for auth credentials + --with-openssl This option does NOT affect the Subversion build + process in any way. It tells an integrated Serf HTTP + client library build process where to locate the + OpenSSL library when (and only when) building Serf + as an integrated part of the Subversion build + process. When linking to a previously installed + version of Serf instead, you do not need to use this + option. + --with-editor=PATH Specify a default editor for the subversion client. + --with-zlib=PREFIX zlib compression library + --with-jdk=PATH Try to use 'PATH/include' to find the JNI headers. + If PATH is not specified, look for a Java + Development Kit at JAVA_HOME. + --with-jikes=PATH Specify the path to a jikes binary to use it as your + Java compiler. The default is to look for jikes + (PATH optional). This behavior can be switched off + by supplying 'no'. + --with-swig=PATH Try to use 'PATH/bin/swig' to build the swig + bindings. If PATH is not specified, look for a + 'swig' binary in your PATH. + --with-ruby-sitedir=SITEDIR + install Ruby bindings in SITEDIR (default is same as + ruby's one) + --with-ruby-test-verbose=LEVEL + how to use output level for Ruby bindings tests + (default is normal) + --with-ctypesgen=PATH Specify the path to ctypesgen. This can either be + the full path to a ctypesgen installation, the full + path to a ctypesgen source tree or the full path to + ctypesgen.py. + --with-junit=PATH Specify a path to the junit JAR file. + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CXX C++ compiler command + CXXFLAGS C++ compiler flags + CPP C preprocessor + CXXCPP C++ preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to . +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +subversion configure 1.8.0 +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_cxx_try_compile LINENO +# ---------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} +( $as_echo "## -------------------------------------------- ## +## Report this to http://subversion.apache.org/ ## +## -------------------------------------------- ##" + ) | sed "s/^/$as_me: WARNING: /" >&2 + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func + +# ac_fn_cxx_try_cpp LINENO +# ------------------------ +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_cpp + +# ac_fn_cxx_try_link LINENO +# ------------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_link + +# ac_fn_c_check_type LINENO TYPE VAR INCLUDES +# ------------------------------------------- +# Tests whether TYPE exists after having included INCLUDES, setting cache +# variable VAR accordingly. +ac_fn_c_check_type () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=no" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +if (sizeof ($2)) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +if (sizeof (($2))) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + eval "$3=yes" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_type +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by subversion $as_me 1.8.0, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + +ac_aux_dir= +for ac_dir in build "$srcdir"/build; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in build \"$srcdir\"/build" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Subversion 1.8.0" >&5 +$as_echo "$as_me: Configuring Subversion 1.8.0" >&6;} + +abs_srcdir="`cd $srcdir && pwd`" + +abs_builddir="`pwd`" + +if test "$abs_srcdir" = "$abs_builddir"; then + canonicalized_srcdir="" +else + canonicalized_srcdir="$srcdir/" +fi + + +SWIG_LDFLAGS="$LDFLAGS" + + +# Generate config.nice early (before the arguments are munged) + + { $as_echo "$as_me:${as_lineno-$LINENO}: creating config.nice" >&5 +$as_echo "$as_me: creating config.nice" >&6;} + # This little dance satisfies Cygwin, which cannot overwrite in-use files. + if test -f "config.nice"; then + mv "config.nice" "config.nice.old" + fi + + cat >"config.nice" <&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + CFLAGS_KEEP="$CFLAGS" + CFLAGS="" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -std=c90" >&5 +$as_echo_n "checking if $CC accepts -std=c90... " >&6; } + CFLAGS="-std=c90 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -std=c89" >&5 +$as_echo_n "checking if $CC accepts -std=c89... " >&6; } + CFLAGS="-std=c89 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -ansi" >&5 +$as_echo_n "checking if $CC accepts -ansi... " >&6; } + CFLAGS="-ansi $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + CMODEFLAGS="$CFLAGS" + CFLAGS="$CFLAGS_KEEP" + + + + +# Look for a C++ compiler (before anything can set CXXFLAGS) +CXXMAINTAINERFLAGS="$CXXUSERFLAGS" +CXXUSERFLAGS="$CXXFLAGS" +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +if test -z "$CXX"; then + if test -n "$CCC"; then + CXX=$CCC + else + if test -n "$ac_tool_prefix"; then + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CXX"; then + ac_cv_prog_CXX="$CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CXX=$ac_cv_prog_CXX +if test -n "$CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 +$as_echo "$CXX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CXX" && break + done +fi +if test -z "$CXX"; then + ac_ct_CXX=$CXX + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CXX"; then + ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CXX="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CXX=$ac_cv_prog_ac_ct_CXX +if test -n "$ac_ct_CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 +$as_echo "$ac_ct_CXX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CXX" && break +done + + if test "x$ac_ct_CXX" = x; then + CXX="g++" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CXX=$ac_ct_CXX + fi +fi + + fi +fi +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5 +$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; } +if ${ac_cv_cxx_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_cxx_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 +$as_echo "$ac_cv_cxx_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GXX=yes +else + GXX= +fi +ac_test_CXXFLAGS=${CXXFLAGS+set} +ac_save_CXXFLAGS=$CXXFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 +$as_echo_n "checking whether $CXX accepts -g... " >&6; } +if ${ac_cv_prog_cxx_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_cxx_werror_flag=$ac_cxx_werror_flag + ac_cxx_werror_flag=yes + ac_cv_prog_cxx_g=no + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_prog_cxx_g=yes +else + CXXFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + +else + ac_cxx_werror_flag=$ac_save_cxx_werror_flag + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_prog_cxx_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cxx_werror_flag=$ac_save_cxx_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 +$as_echo "$ac_cv_prog_cxx_g" >&6; } +if test "$ac_test_CXXFLAGS" = set; then + CXXFLAGS=$ac_save_CXXFLAGS +elif test $ac_cv_prog_cxx_g = yes; then + if test "$GXX" = yes; then + CXXFLAGS="-g -O2" + else + CXXFLAGS="-g" + fi +else + if test "$GXX" = yes; then + CXXFLAGS="-O2" + else + CXXFLAGS= + fi +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + CXXFLAGS_KEEP="$CXXFLAGS" + CXXFLAGS="" + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -std=c++98" >&5 +$as_echo_n "checking if $CXX accepts -std=c++98... " >&6; } + CXXFLAGS="-std=c++98 $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + CXXMODEFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS_KEEP" + + + + +# Look for a C pre-processor +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +# Look for a good sed +# AC_PROG_SED was introduced in Autoconf 2.59b +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 +$as_echo_n "checking for a sed that does not truncate output... " >&6; } +if ${ac_cv_path_SED+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for ac_i in 1 2 3 4 5 6 7; do + ac_script="$ac_script$as_nl$ac_script" + done + echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed + { ac_script=; unset ac_script;} + if test -z "$SED"; then + ac_path_SED_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_SED" || continue +# Check for GNU ac_path_SED and select it if it is found. + # Check for GNU $ac_path_SED +case `"$ac_path_SED" --version 2>&1` in +*GNU*) + ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo '' >> "conftest.nl" + "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_SED_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_SED="$ac_path_SED" + ac_path_SED_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_SED_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_SED"; then + as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 + fi +else + ac_cv_path_SED=$SED +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 +$as_echo "$ac_cv_path_SED" >&6; } + SED="$ac_cv_path_SED" + rm -f conftest.sed + + +# Grab target_cpu, so we can use it in the Solaris pkginfo file +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if ${ac_cv_build+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if ${ac_cv_host+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking target system type" >&5 +$as_echo_n "checking target system type... " >&6; } +if ${ac_cv_target+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$target_alias" = x; then + ac_cv_target=$ac_cv_host +else + ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_target" >&5 +$as_echo "$ac_cv_target" >&6; } +case $ac_cv_target in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical target" "$LINENO" 5;; +esac +target=$ac_cv_target +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_target +shift +target_cpu=$1 +target_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +target_os=$* +IFS=$ac_save_IFS +case $target_os in *\ *) target_os=`echo "$target_os" | sed 's/ /-/g'`;; esac + + +# The aliases save the names the user supplied, while $host etc. +# will get canonicalized. +test -n "$target_alias" && + test "$program_prefix$program_suffix$program_transform_name" = \ + NONENONEs,x,x, && + program_prefix=${target_alias}- + +# Look for an extended grep +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 +$as_echo_n "checking whether ln -s works... " >&6; } +LN_S=$as_ln_s +if test "$LN_S" = "ln -s"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 +$as_echo "no, using $LN_S" >&6; } +fi + + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +# If $INSTALL is relative path to our fallback install-sh, then convert +# to an absolute path, as in some cases (e.g. Solaris VPATH build), libtool +# may try to use it from a changed working directory. +if test "$INSTALL" = "build/install-sh -c"; then + INSTALL="$abs_srcdir/$INSTALL" +fi + +if test -z "$MKDIR"; then + MKDIR="$INSTALL -d" +fi + + +# ==== Libraries, for which we may have source to build ====================== + + +APR_VER_REGEXES="0\.9\.[7-9] 0\.9\.1[0-9] 1\. 2\." + + + APR_WANTED_REGEXES="$APR_VER_REGEXES" + + { $as_echo "$as_me:${as_lineno-$LINENO}: Apache Portable Runtime (APR) library configuration" >&5 +$as_echo "$as_me: Apache Portable Runtime (APR) library configuration" >&6;} + + + apr_found="no" + + if test "$ac_cv_emxos2" = "yes"; then + # Scripts don't pass test -x on OS/2 + TEST_X="test -f" + else + TEST_X="test -x" + fi + + acceptable_majors="1 0" + + apr_temp_acceptable_apr_config="" + for apr_temp_major in $acceptable_majors + do + case $apr_temp_major in + 0) + apr_temp_acceptable_apr_config="$apr_temp_acceptable_apr_config apr-config" + ;; + *) + apr_temp_acceptable_apr_config="$apr_temp_acceptable_apr_config apr-$apr_temp_major-config" + ;; + esac + done + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for APR" >&5 +$as_echo_n "checking for APR... " >&6; } + +# Check whether --with-apr was given. +if test "${with_apr+set}" = set; then : + withval=$with_apr; + if test "$withval" = "no" || test "$withval" = "yes"; then + as_fn_error $? "--with-apr requires a directory or file to be provided" "$LINENO" 5 + fi + + for apr_temp_apr_config_file in $apr_temp_acceptable_apr_config + do + for lookdir in "$withval/bin" "$withval" + do + if $TEST_X "$lookdir/$apr_temp_apr_config_file"; then + apr_found="yes" + apr_config="$lookdir/$apr_temp_apr_config_file" + break 2 + fi + done + done + + if test "$apr_found" != "yes" && $TEST_X "$withval" && $withval --help > /dev/null 2>&1 ; then + apr_found="yes" + apr_config="$withval" + fi + + if test "$apr_found" != "yes"; then + as_fn_error $? "the --with-apr parameter is incorrect. It must specify an install prefix, a build directory, or an apr-config file." "$LINENO" 5 + fi + +else + + if test -d """"; then + apr_temp_abs_srcdir="`cd "" && pwd`" + apr_found="reconfig" + apr_bundled_major="`sed -n '/#define.*APR_MAJOR_VERSION/s/^[^0-9]*\([0-9]*\).*$/\1/p' \"""/include/apr_version.h\"`" + case $apr_bundled_major in + "") + as_fn_error $? "failed to find major version of bundled APR" "$LINENO" 5 + ;; + 0) + apr_temp_apr_config_file="apr-config" + ;; + *) + apr_temp_apr_config_file="apr-$apr_bundled_major-config" + ;; + esac + if test -n """"; then + apr_config="""/$apr_temp_apr_config_file" + else + apr_config="""/$apr_temp_apr_config_file" + fi + fi + if test "$apr_found" = "no" && test -n "1" && test "1" = "1"; then + for apr_temp_apr_config_file in $apr_temp_acceptable_apr_config + do + if $apr_temp_apr_config_file --help > /dev/null 2>&1 ; then + apr_found="yes" + apr_config="$apr_temp_apr_config_file" + break + else + for lookdir in /usr /usr/local /opt/apr /usr/local/apache2 ; do + if $TEST_X "$lookdir/bin/$apr_temp_apr_config_file"; then + apr_found="yes" + apr_config="$lookdir/bin/$apr_temp_apr_config_file" + break 2 + fi + done + fi + done + fi + +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $apr_found" >&5 +$as_echo "$apr_found" >&6; } + + + if test $apr_found = "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: APR not found" >&5 +$as_echo "$as_me: WARNING: APR not found" >&2;} + + echo "The Apache Portable Runtime (APR) library cannot be found." + echo "Please install APR on this system and configure Subversion" + echo "with the appropriate --with-apr option." + echo "" + echo "You probably need to do something similar with the Apache" + echo "Portable Runtime Utility (APRUTIL) library and then configure" + echo "Subversion with both the --with-apr and --with-apr-util options." + echo "" + as_fn_error $? "no suitable APR found" "$LINENO" 5 + + fi + + if test $apr_found = "reconfig"; then + as_fn_error $? "Unexpected APR reconfig" "$LINENO" 5 + fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking APR version" >&5 +$as_echo_n "checking APR version... " >&6; } + apr_version="`$apr_config --version`" + if test $? -ne 0; then + as_fn_error $? "apr-config --version failed" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $apr_version" >&5 +$as_echo "$apr_version" >&6; } + + APR_WANTED_REGEX_MATCH=0 + for apr_wanted_regex in $APR_WANTED_REGEXES; do + if test `expr $apr_version : $apr_wanted_regex` -ne 0; then + APR_WANTED_REGEX_MATCH=1 + break + fi + done + + if test $APR_WANTED_REGEX_MATCH -eq 0; then + echo "wanted regexes are $APR_WANTED_REGEXES" + as_fn_error $? "invalid apr version found" "$LINENO" 5 + fi + + + CPPFLAGS="$CPPFLAGS `$apr_config --cppflags`" + if test $? -ne 0; then + as_fn_error $? "apr-config --cppflags failed" "$LINENO" 5 + fi + + CFLAGS="$CFLAGS `$apr_config --cflags`" + if test $? -ne 0; then + as_fn_error $? "apr-config --cflags failed" "$LINENO" 5 + fi + + apr_ldflags="`$apr_config --ldflags`" + if test $? -ne 0; then + as_fn_error $? "apr-config --ldflags failed" "$LINENO" 5 + fi + LDFLAGS="$LDFLAGS ` + input_flags="$apr_ldflags" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + SVN_APR_INCLUDES="`$apr_config --includes`" + if test $? -ne 0; then + as_fn_error $? "apr-config --includes failed" "$LINENO" 5 + fi + + if test "$enable_all_static" = "yes"; then + SVN_APR_LIBS="`$apr_config --link-ld --libs`" + if test $? -ne 0; then + as_fn_error $? "apr-config --link-ld --libs failed" "$LINENO" 5 + fi + else + SVN_APR_LIBS="`$apr_config --link-ld`" + if test $? -ne 0; then + as_fn_error $? "apr-config --link-ld failed" "$LINENO" 5 + fi + fi + SVN_APR_LIBS="` + input_flags="$SVN_APR_LIBS" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + SVN_APR_SHLIB_PATH_VAR="`$apr_config --shlib-path-var`" + if test $? -ne 0; then + as_fn_error $? "apr-config --shlib-path-var failed" "$LINENO" 5 + fi + + SVN_APR_CONFIG="$apr_config" + + + + + + +if test `expr $apr_version : 2` -ne 0; then + svn_lib_ver=2 + apu_config=$apr_config + + SVN_APRUTIL_CONFIG="$apu_config" + + +else + svn_lib_ver=0 + APU_VER_REGEXES="0\.9\.[7-9] 0\.9\.1[0-9] 1\." + + APRUTIL_WANTED_REGEXES="$APU_VER_REGEXES" + + { $as_echo "$as_me:${as_lineno-$LINENO}: Apache Portable Runtime Utility (APRUTIL) library configuration" >&5 +$as_echo "$as_me: Apache Portable Runtime Utility (APRUTIL) library configuration" >&6;} + + + apu_found="no" + + if test "$ac_cv_emxos2" = "yes"; then + # Scripts don't pass test -x on OS/2 + TEST_X="test -f" + else + TEST_X="test -x" + fi + + acceptable_majors="1 0" + + apu_temp_acceptable_apu_config="" + for apu_temp_major in $acceptable_majors + do + case $apu_temp_major in + 0) + apu_temp_acceptable_apu_config="$apu_temp_acceptable_apu_config apu-config" + ;; + *) + apu_temp_acceptable_apu_config="$apu_temp_acceptable_apu_config apu-$apu_temp_major-config" + ;; + esac + done + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for APR-util" >&5 +$as_echo_n "checking for APR-util... " >&6; } + +# Check whether --with-apr-util was given. +if test "${with_apr_util+set}" = set; then : + withval=$with_apr_util; + if test "$withval" = "no" || test "$withval" = "yes"; then + as_fn_error $? "--with-apr-util requires a directory or file to be provided" "$LINENO" 5 + fi + + for apu_temp_apu_config_file in $apu_temp_acceptable_apu_config + do + for lookdir in "$withval/bin" "$withval" + do + if $TEST_X "$lookdir/$apu_temp_apu_config_file"; then + apu_found="yes" + apu_config="$lookdir/$apu_temp_apu_config_file" + break 2 + fi + done + done + + if test "$apu_found" != "yes" && $TEST_X "$withval" && $withval --help > /dev/null 2>&1 ; then + apu_found="yes" + apu_config="$withval" + fi + + if test "$apu_found" != "yes"; then + as_fn_error $? "the --with-apr-util parameter is incorrect. It must specify an install prefix, a build directory, or an apu-config file." "$LINENO" 5 + fi + +else + + if test -d """"; then + apu_temp_abs_srcdir="`cd "" && pwd`" + apu_found="reconfig" + apu_bundled_major="`sed -n '/#define.*APU_MAJOR_VERSION/s/^[^0-9]*\([0-9]*\).*$/\1/p' \"""/include/apu_version.h\"`" + case $apu_bundled_major in + "") + as_fn_error $? "failed to find major version of bundled APU" "$LINENO" 5 + ;; + 0) + apu_temp_apu_config_file="apu-config" + ;; + *) + apu_temp_apu_config_file="apu-$apu_bundled_major-config" + ;; + esac + if test -n """"; then + apu_config="""/$apu_temp_apu_config_file" + else + apu_config="""/$apu_temp_apu_config_file" + fi + fi + if test "$apu_found" = "no" && test -n "1" && test "1" = "1"; then + for apu_temp_apu_config_file in $apu_temp_acceptable_apu_config + do + if $apu_temp_apu_config_file --help > /dev/null 2>&1 ; then + apu_found="yes" + apu_config="$apu_temp_apu_config_file" + break + else + for lookdir in /usr /usr/local /opt/apr /usr/local/apache2 ; do + if $TEST_X "$lookdir/bin/$apu_temp_apu_config_file"; then + apu_found="yes" + apu_config="$lookdir/bin/$apu_temp_apu_config_file" + break 2 + fi + done + fi + done + fi + +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $apu_found" >&5 +$as_echo "$apu_found" >&6; } + + + if test $apu_found = "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: APRUTIL not found" >&5 +$as_echo "$as_me: WARNING: APRUTIL not found" >&2;} + + echo "The Apache Portable Runtime Utility (APRUTIL) library cannot be found." + echo "Install APRUTIL on this system and configure Subversion with the" + echo " appropriate --with-apr-util option." + echo "" + as_fn_error $? "no suitable APRUTIL found" "$LINENO" 5 + + fi + + if test $apu_found = "reconfig"; then + as_fn_error $? "Unexpected APRUTIL reconfig" "$LINENO" 5 + fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking APRUTIL version" >&5 +$as_echo_n "checking APRUTIL version... " >&6; } + apu_version="`$apu_config --version`" + if test $? -ne 0; then + # This is a hack as suggested by Ben Collins-Sussman. It can be + # removed after apache 2.0.44 has been released. (The apu-config + # shipped in 2.0.43 contains a correct version number, but + # stupidly doesn't understand the --version switch.) + apu_version=`grep "APRUTIL_DOTTED_VERSION=" $(which $apu_config) | tr -d "APRUTIL_DOTTED_VERSION="| tr -d '"'` + #AC_MSG_ERROR([ + # apu-config --version failed. + # Your apu-config doesn't support the --version switch, please upgrade + # to APR-UTIL more recent than 2002-Nov-05.]) + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $apu_version" >&5 +$as_echo "$apu_version" >&6; } + + APU_WANTED_REGEX_MATCH=0 + for apu_wanted_regex in $APRUTIL_WANTED_REGEXES; do + if test `expr $apu_version : $apu_wanted_regex` -ne 0; then + APU_WANTED_REGEX_MATCH=1 + break + fi + done + + if test $APU_WANTED_REGEX_MATCH -eq 0; then + echo "wanted regexes are $APRUTIL_WANTED_REGEXES" + as_fn_error $? "invalid APRUTIL version found" "$LINENO" 5 + fi + + + apu_ldflags="`$apu_config --ldflags`" + if test $? -ne 0; then + as_fn_error $? "apu-config --ldflags failed" "$LINENO" 5 + fi + LDFLAGS="$LDFLAGS ` + input_flags="$apu_ldflags" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + SVN_APRUTIL_INCLUDES="`$apu_config --includes`" + if test $? -ne 0; then + as_fn_error $? "apu-config --includes failed" "$LINENO" 5 + fi + + if test "$enable_all_static" = "yes"; then + SVN_APRUTIL_LIBS="`$apu_config --link-ld --libs`" + if test $? -ne 0; then + as_fn_error $? "apu-config --link-ld --libs failed" "$LINENO" 5 + fi + else + SVN_APRUTIL_LIBS="`$apu_config --link-ld`" + if test $? -ne 0; then + as_fn_error $? "apu-config --link-ld failed" "$LINENO" 5 + fi + fi + SVN_APRUTIL_LIBS="` + input_flags="$SVN_APRUTIL_LIBS" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + + SVN_APRUTIL_CONFIG="$apu_config" + + + + SVN_HAVE_OLD_EXPAT="`$apu_config --old-expat`" + if test "$SVN_HAVE_OLD_EXPAT" = "yes"; then + +$as_echo "#define SVN_HAVE_OLD_EXPAT 1" >>confdefs.h + + fi + +fi +SVN_LT_SOVERSION="-version-info $svn_lib_ver" + + +cat >>confdefs.h <<_ACEOF +#define SVN_SOVERSION $svn_lib_ver +_ACEOF + + +# Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + + + serf_found=no + serf_required=no + serf_skip=no + + serf_check_major="1" + serf_check_minor="2" + serf_check_patch="1" + serf_check_version="1.2.1" + + +# Check whether --with-serf was given. +if test "${with_serf+set}" = set; then : + withval=$with_serf; + if test "$withval" = "yes" ; then + serf_required=yes + elif test "$withval" = "no" ; then + serf_skip=yes + else + + { $as_echo "$as_me:${as_lineno-$LINENO}: serf library configuration via prefix" >&5 +$as_echo "$as_me: serf library configuration via prefix" >&6;} + serf_required=yes + serf_prefix=$withval + for serf_major in serf-2 serf-1; do + if ! test -d $serf_prefix/include/$serf_major; then continue; fi + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES -I$serf_prefix/include/$serf_major" + for ac_header in serf.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "serf.h" "ac_cv_header_serf_h" "$ac_includes_default" +if test "x$ac_cv_header_serf_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_SERF_H 1 +_ACEOF + + save_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS -L$serf_prefix/lib" + as_ac_Lib=`$as_echo "ac_cv_lib_$serf_major''_serf_context_create" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for serf_context_create in -l$serf_major" >&5 +$as_echo_n "checking for serf_context_create in -l$serf_major... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$serf_major $SVN_APRUTIL_LIBS $SVN_APR_LIBS -lz $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char serf_context_create (); +int +main () +{ +return serf_context_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#include "serf.h" + +int +main () +{ + +#if ! SERF_VERSION_AT_LEAST($serf_check_major, $serf_check_minor, $serf_check_patch) +#error Serf version too old: need $serf_check_version +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + serf_found=yes +else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Serf version too old: need $serf_check_version" >&5 +$as_echo "$as_me: WARNING: Serf version too old: need $serf_check_version" >&2;} + serf_found=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + + LDFLAGS="$save_ldflags" +fi + +done + + CPPFLAGS="$save_cppflags" + test $serf_found = yes && break + done + + if test $serf_found = "yes"; then + SVN_SERF_INCLUDES="-I$serf_prefix/include/$serf_major" + if test -e "$serf_prefix/lib/lib$serf_major.la"; then + SVN_SERF_LIBS="$serf_prefix/lib/lib$serf_major.la" + else + SVN_SERF_LIBS="-l$serf_major" + LDFLAGS="$LDFLAGS -L$serf_prefix/lib" + fi + fi + + fi + +fi + + + if test "$serf_skip" = "no" ; then + if test "$serf_found" = "no" ; then + + { $as_echo "$as_me:${as_lineno-$LINENO}: serf library configuration via pkg-config" >&5 +$as_echo "$as_me: serf library configuration via pkg-config" >&6;} + if test -n "$PKG_CONFIG"; then + for serf_major in serf-2 serf-1; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $serf_major library" >&5 +$as_echo_n "checking for $serf_major library... " >&6; } + if $PKG_CONFIG $serf_major --exists; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking serf library version" >&5 +$as_echo_n "checking serf library version... " >&6; } + SERF_VERSION=`$PKG_CONFIG $serf_major --modversion` + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SERF_VERSION" >&5 +$as_echo "$SERF_VERSION" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking serf version is suitable" >&5 +$as_echo_n "checking serf version is suitable... " >&6; } + if $PKG_CONFIG $serf_major --atleast-version=$serf_check_version; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + serf_found=yes + SVN_SERF_INCLUDES=`$PKG_CONFIG $serf_major --cflags | $SED -e 's/-D[^ ]*//g'` + SVN_SERF_LIBS=`$PKG_CONFIG $serf_major --libs` + break + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Serf version too old: need $serf_check_version" >&5 +$as_echo "$as_me: WARNING: Serf version too old: need $serf_check_version" >&2;} + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + done + fi + + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking was serf enabled" >&5 +$as_echo_n "checking was serf enabled... " >&6; } + if test "$serf_found" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + + echo "" + echo "An appropriate version of serf could not be found, so libsvn_ra_serf" + echo "will not be built. If you want to build libsvn_ra_serf, please" + echo "install serf $serf_check_version or newer." + echo "" + + if test "$serf_required" = "yes"; then + as_fn_error $? "Serf was explicitly enabled but an appropriate version was not found." "$LINENO" 5 + fi + fi + fi + + svn_lib_serf=$serf_found + + + + + +if test "$svn_lib_serf" = "yes"; then + +$as_echo "#define SVN_HAVE_SERF 1" >>confdefs.h + +fi + + + apr_memcache_found=no + + +# Check whether --with-apr_memcache was given. +if test "${with_apr_memcache+set}" = set; then : + withval=$with_apr_memcache; + if test "$withval" = "yes" ; then + as_fn_error $? "--with-apr_memcache requires an argument." "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: looking for separate apr_memcache package" >&5 +$as_echo "$as_me: looking for separate apr_memcache package" >&6;} + apr_memcache_prefix=$withval + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES -I$apr_memcache_prefix/include/apr_memcache-0" + ac_fn_c_check_header_mongrel "$LINENO" "apr_memcache.h" "ac_cv_header_apr_memcache_h" "$ac_includes_default" +if test "x$ac_cv_header_apr_memcache_h" = xyes; then : + + save_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS -L$apr_memcache_prefix/lib" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_memcache_create in -lapr_memcache" >&5 +$as_echo_n "checking for apr_memcache_create in -lapr_memcache... " >&6; } +if ${ac_cv_lib_apr_memcache_apr_memcache_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lapr_memcache $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char apr_memcache_create (); +int +main () +{ +return apr_memcache_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_apr_memcache_apr_memcache_create=yes +else + ac_cv_lib_apr_memcache_apr_memcache_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_apr_memcache_apr_memcache_create" >&5 +$as_echo "$ac_cv_lib_apr_memcache_apr_memcache_create" >&6; } +if test "x$ac_cv_lib_apr_memcache_apr_memcache_create" = xyes; then : + apr_memcache_found="standalone" +fi + + LDFLAGS="$save_ldflags" +fi + + + CPPFLAGS="$save_cppflags" + fi + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: looking for apr_memcache as part of apr-util" >&5 +$as_echo "$as_me: looking for apr_memcache as part of apr-util" >&6;} + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES" + ac_fn_c_check_header_mongrel "$LINENO" "apr_memcache.h" "ac_cv_header_apr_memcache_h" "$ac_includes_default" +if test "x$ac_cv_header_apr_memcache_h" = xyes; then : + + save_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS $SVN_APRUTIL_LIBS" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_memcache_create in -laprutil-1" >&5 +$as_echo_n "checking for apr_memcache_create in -laprutil-1... " >&6; } +if ${ac_cv_lib_aprutil_1_apr_memcache_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-laprutil-1 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char apr_memcache_create (); +int +main () +{ +return apr_memcache_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_aprutil_1_apr_memcache_create=yes +else + ac_cv_lib_aprutil_1_apr_memcache_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_aprutil_1_apr_memcache_create" >&5 +$as_echo "$ac_cv_lib_aprutil_1_apr_memcache_create" >&6; } +if test "x$ac_cv_lib_aprutil_1_apr_memcache_create" = xyes; then : + apr_memcache_found="aprutil" +fi + + LDFLAGS="$save_ldflags" +fi + + + CPPFLAGS="$save_cppflags" + +fi + + + + if test $apr_memcache_found = "standalone"; then + SVN_APR_MEMCACHE_INCLUDES="-I$apr_memcache_prefix/include/apr_memcache-0" + SVN_APR_MEMCACHE_LIBS="$apr_memcache_prefix/lib/libapr_memcache.la" + svn_lib_apr_memcache=yes + elif test $apr_memcache_found = "aprutil"; then + SVN_APR_MEMCACHE_INCLUDES="" + SVN_APR_MEMCACHE_LIBS="" + svn_lib_apr_memcache=yes + elif test $apr_memcache_found = "reconfig"; then + svn_lib_apr_memcache=yes + else + svn_lib_apr_memcache=no + fi + + + + + +if test "$svn_lib_apr_memcache" = "yes"; then + +$as_echo "#define SVN_HAVE_MEMCACHE 1" >>confdefs.h + +fi + + + + + +HTTPD_WANTED_MMN="20020903" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Apache module support via DSO through APXS" >&5 +$as_echo_n "checking for Apache module support via DSO through APXS... " >&6; } + +# Check whether --with-apxs was given. +if test "${with_apxs+set}" = set; then : + withval=$with_apxs; + if test "$withval" = "yes"; then + APXS=apxs + else + APXS="$withval" + fi + APXS_EXPLICIT=1 + +fi + + +if test -z "$APXS"; then + for i in /usr/sbin /usr/local/apache/bin /usr/local/apache2/bin /usr/bin ; do + if test -f "$i/apxs2"; then + APXS="$i/apxs2" + break + fi + if test -f "$i/apxs"; then + APXS="$i/apxs" + break + fi + done +fi + +if test -n "$APXS" && test "$APXS" != "no"; then + APXS_INCLUDE="`$APXS -q INCLUDEDIR`" + if test -r $APXS_INCLUDE/mod_dav.h; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: found at $APXS" >&5 +$as_echo "found at $APXS" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking httpd version" >&5 +$as_echo_n "checking httpd version... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include "$APXS_INCLUDE/ap_mmn.h" +#if AP_MODULE_MAGIC_AT_LEAST($HTTPD_WANTED_MMN,0) +VERSION_OKAY +#endif +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "VERSION_OKAY" >/dev/null 2>&1; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: recent enough" >&5 +$as_echo "recent enough" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: apache too old: mmn must be at least $HTTPD_WANTED_MMN" >&5 +$as_echo "apache too old: mmn must be at least $HTTPD_WANTED_MMN" >&6; } + if test "$APXS_EXPLICIT" != ""; then + as_fn_error $? "Apache APXS build explicitly requested, but apache version is too old" "$LINENO" 5 + fi + APXS="" + +fi +rm -f conftest* + + + elif test "$APXS_EXPLICIT" != ""; then + as_fn_error $? "no - APXS refers to an old version of Apache + Unable to locate $APXS_INCLUDE/mod_dav.h" "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no - Unable to locate $APXS_INCLUDE/mod_dav.h" >&5 +$as_echo "no - Unable to locate $APXS_INCLUDE/mod_dav.h" >&6; } + APXS="" + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + +if test -n "$APXS" && test "$APXS" != "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether Apache version is compatible with APR version" >&5 +$as_echo_n "checking whether Apache version is compatible with APR version... " >&6; } + apr_major_version="${apr_version%%.*}" + case "$apr_major_version" in + 0) + apache_minor_version_wanted_regex="0" + ;; + 1) + apache_minor_version_wanted_regex="[1-5]" + ;; + 2) + apache_minor_version_wanted_regex="[3-5]" + ;; + *) + as_fn_error $? "unknown APR version" "$LINENO" 5 + ;; + esac + old_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include "$APXS_INCLUDE/ap_release.h" +apache_minor_version=AP_SERVER_MINORVERSION +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "apache_minor_version= *\"$apache_minor_version_wanted_regex\"" >/dev/null 2>&1; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "Apache version incompatible with APR version" "$LINENO" 5 +fi +rm -f conftest* + + CPPFLAGS="$old_CPPFLAGS" +fi + + +# Check whether --with-apache-libexecdir was given. +if test "${with_apache_libexecdir+set}" = set; then : + withval=$with_apache_libexecdir; APACHE_LIBEXECDIR="$withval" +else + APACHE_LIBEXECDIR='no' +fi + + +INSTALL_APACHE_MODS=false +if test -n "$APXS" && test "$APXS" != "no"; then + APXS_CC="`$APXS -q CC`" + APACHE_INCLUDES="$APACHE_INCLUDES -I$APXS_INCLUDE" + + if test "$APACHE_LIBEXECDIR" = 'no'; then + APACHE_LIBEXECDIR="$libexecdir" + elif test "$APACHE_LIBEXECDIR" = 'yes'; then + APACHE_LIBEXECDIR="`$APXS -q libexecdir`" + fi + + BUILD_APACHE_RULE=apache-mod + INSTALL_APACHE_RULE=install-mods-shared + INSTALL_APACHE_MODS=true + + case $host in + *-*-cygwin*) + APACHE_LDFLAGS="-shrext .so" + ;; + esac +elif test x"$APXS" != x"no"; then + echo "==================================================================" + echo "WARNING: skipping the build of mod_dav_svn" + echo " try using --with-apxs" + echo "==================================================================" +fi + + + + + + + +# there aren't any flags that interest us ... +#if test -n "$APXS" && test "$APXS" != "no"; then +# CFLAGS="$CFLAGS `$APXS -q CFLAGS CFLAGS_SHLIB`" +#fi + +if test -n "$APXS_CC" && test "$APXS_CC" != "$CC" ; then + echo "==================================================================" + echo "WARNING: You have chosen to compile Subversion with a different" + echo " compiler than the one used to compile Apache." + echo "" + echo " Current compiler: $CC" + echo " Apache's compiler: $APXS_CC" + echo "" + echo "This could cause some problems." + echo "==================================================================" +fi + + + +SQLITE_MINIMUM_VER="3.7.12" +SQLITE_RECOMMENDED_VER="3.7.15.1" +SQLITE_URL="http://www.sqlite.org/sqlite-amalgamation-$(printf %d%02d%02d%02d $(echo ${SQLITE_RECOMMENDED_VER} | sed -e 's/\./ /g')).zip" + + + SQLITE_MINIMUM_VER="${SQLITE_MINIMUM_VER}" + SQLITE_RECOMMENDED_VER="${SQLITE_RECOMMENDED_VER}" + SQLITE_URL="${SQLITE_URL}" + SQLITE_PKGNAME="sqlite3" + + + + version_string="$SQLITE_MINIMUM_VER" + + major=`expr $version_string : '\([0-9]*\)'` + minor=`expr $version_string : '[0-9]*\.\([0-9]*\)'` + micro=`expr $version_string : '[0-9]*\.[0-9]*\.\([0-9]*\)'` + if test -z "$micro"; then + micro=0 + fi + sqlite_min_ver_num=`expr $major \* 1000000 \ + \+ $minor \* 1000 \ + \+ $micro` + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite library" >&5 +$as_echo "$as_me: checking sqlite library" >&6;} + + +# Check whether --with-sqlite was given. +if test "${with_sqlite+set}" = set; then : + withval=$with_sqlite; + if test "$withval" = "yes" ; then + as_fn_error $? "--with-sqlite requires an argument." "$LINENO" 5 + else + sqlite_dir="$withval" + fi + + if test -d $sqlite_dir; then + + if test -z "$sqlite_dir"; then + sqlite_dir="" + sqlite_include="sqlite3.h" + else + sqlite_dir="$sqlite_dir" + sqlite_include="$sqlite_dir/include/sqlite3.h" + fi + + save_CPPFLAGS="$CPPFLAGS" + save_LDFLAGS="$LDFLAGS" + + if test ! -z "$sqlite_dir"; then + CPPFLAGS="$CPPFLAGS -I$sqlite_dir/include" + LDFLAGS="$LDFLAGS -L$sqlite_dir/lib" + fi + + ac_fn_c_check_header_mongrel "$LINENO" "sqlite3.h" "ac_cv_header_sqlite3_h" "$ac_includes_default" +if test "x$ac_cv_header_sqlite3_h" = xyes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite library version (via header)" >&5 +$as_echo_n "checking sqlite library version (via header)... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include "$sqlite_include" +#if SQLITE_VERSION_NUMBER >= $sqlite_min_ver_num +SQLITE_VERSION_OKAY +#endif +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "SQLITE_VERSION_OKAY" >/dev/null 2>&1; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: okay" >&5 +$as_echo "okay" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_close in -lsqlite3" >&5 +$as_echo_n "checking for sqlite3_close in -lsqlite3... " >&6; } +if ${ac_cv_lib_sqlite3_sqlite3_close+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsqlite3 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char sqlite3_close (); +int +main () +{ +return sqlite3_close (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_sqlite3_sqlite3_close=yes +else + ac_cv_lib_sqlite3_sqlite3_close=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sqlite3_sqlite3_close" >&5 +$as_echo "$ac_cv_lib_sqlite3_sqlite3_close" >&6; } +if test "x$ac_cv_lib_sqlite3_sqlite3_close" = xyes; then : + + svn_lib_sqlite="yes" + if test -z "$sqlite_dir" -o ! -d "$sqlite_dir"; then + SVN_SQLITE_LIBS="-lsqlite3" + else + SVN_SQLITE_INCLUDES="-I$sqlite_dir/include" + SVN_SQLITE_LIBS="` + input_flags="-L$sqlite_dir/lib -lsqlite3" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + fi + +fi + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported SQLite version" >&5 +$as_echo "unsupported SQLite version" >&6; } +fi +rm -f conftest* + + +fi + + + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + + else + + sqlite_amalg="$sqlite_dir" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite amalgamation" >&5 +$as_echo_n "checking sqlite amalgamation... " >&6; } + if test ! -e $sqlite_amalg; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite amalgamation file version" >&5 +$as_echo_n "checking sqlite amalgamation file version... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include "$sqlite_amalg" +#if SQLITE_VERSION_NUMBER >= $sqlite_min_ver_num +SQLITE_VERSION_OKAY +#endif +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "SQLITE_VERSION_OKAY" >/dev/null 2>&1; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: amalgamation found and is okay" >&5 +$as_echo "amalgamation found and is okay" >&6; } + + case $host_os in + beos* | mingw* | pw32* | cegcc* | cygwin*) + svn_sqlite_dso_ldflags= + ;; + + darwin*) + # if libdl is installed we need to link against it + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen="dlopen" svn_sqlite_dso_ldflags="-ldl" +else + + svn_sqlite_dso_ldflags= + +fi + + ;; + + *) + ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" +if test "x$ac_cv_func_shl_load" = xyes; then : + svn_sqlite_dso_ldflags= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + svn_sqlite_dso_ldflags="-ldld" +else + ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" +if test "x$ac_cv_func_dlopen" = xyes; then : + svn_sqlite_dso_ldflags= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + svn_sqlite_dso_ldflags="-ldl" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 +$as_echo_n "checking for dlopen in -lsvld... " >&6; } +if ${ac_cv_lib_svld_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsvld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_svld_dlopen=yes +else + ac_cv_lib_svld_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 +$as_echo "$ac_cv_lib_svld_dlopen" >&6; } +if test "x$ac_cv_lib_svld_dlopen" = xyes; then : + svn_sqlite_dso_ldflags="-lsvld" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 +$as_echo_n "checking for dld_link in -ldld... " >&6; } +if ${ac_cv_lib_dld_dld_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dld_link (); +int +main () +{ +return dld_link (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_dld_link=yes +else + ac_cv_lib_dld_dld_link=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 +$as_echo "$ac_cv_lib_dld_dld_link" >&6; } +if test "x$ac_cv_lib_dld_dld_link" = xyes; then : + svn_sqlite_dso_ldflags="-ldld" +fi + + +fi + + +fi + + +fi + + +fi + + +fi + + ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking additional libraries for sqlite" >&5 +$as_echo_n "checking additional libraries for sqlite... " >&6; } + if test -n "$svn_sqlite_dso_ldflags"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${svn_sqlite_dso_ldflags}" >&5 +$as_echo "${svn_sqlite_dso_ldflags}" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } + fi + + +$as_echo "#define SVN_SQLITE_INLINE 1" >>confdefs.h + + SVN_SQLITE_INCLUDES="-I`dirname $sqlite_amalg`" + if test -n "$svn_sqlite_dso_ldflags"; then + SVN_SQLITE_LIBS="$svn_sqlite_dso_ldflags -lpthread" + else + SVN_SQLITE_LIBS="-lpthread" + fi + svn_lib_sqlite="yes" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported amalgamation SQLite version" >&5 +$as_echo "unsupported amalgamation SQLite version" >&6; } +fi +rm -f conftest* + + fi + + fi + + if test -z "$svn_lib_sqlite"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: no suitable sqlite found in $sqlite_dir" >&5 +$as_echo "$as_me: WARNING: no suitable sqlite found in $sqlite_dir" >&2;} + + echo "" + echo "An appropriate version of sqlite could not be found. We recommmend" + echo "${SQLITE_RECOMMENDED_VER}, but require at least ${SQLITE_MINIMUM_VER}." + echo "Please either install a newer sqlite on this system" + echo "" + echo "or" + echo "" + echo "get the sqlite ${SQLITE_RECOMMENDED_VER} amalgamation from:" + echo " ${SQLITE_URL}" + echo "unpack the archive using unzip and rename the resulting" + echo "directory to:" + echo "$abs_srcdir/sqlite-amalgamation" + echo "" + as_fn_error $? "Subversion requires SQLite" "$LINENO" 5 + + fi + +else + + + sqlite_amalg="$abs_srcdir/sqlite-amalgamation/sqlite3.c" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite amalgamation" >&5 +$as_echo_n "checking sqlite amalgamation... " >&6; } + if test ! -e $sqlite_amalg; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite amalgamation file version" >&5 +$as_echo_n "checking sqlite amalgamation file version... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include "$sqlite_amalg" +#if SQLITE_VERSION_NUMBER >= $sqlite_min_ver_num +SQLITE_VERSION_OKAY +#endif +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "SQLITE_VERSION_OKAY" >/dev/null 2>&1; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: amalgamation found and is okay" >&5 +$as_echo "amalgamation found and is okay" >&6; } + + case $host_os in + beos* | mingw* | pw32* | cegcc* | cygwin*) + svn_sqlite_dso_ldflags= + ;; + + darwin*) + # if libdl is installed we need to link against it + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen="dlopen" svn_sqlite_dso_ldflags="-ldl" +else + + svn_sqlite_dso_ldflags= + +fi + + ;; + + *) + ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" +if test "x$ac_cv_func_shl_load" = xyes; then : + svn_sqlite_dso_ldflags= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + svn_sqlite_dso_ldflags="-ldld" +else + ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" +if test "x$ac_cv_func_dlopen" = xyes; then : + svn_sqlite_dso_ldflags= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + svn_sqlite_dso_ldflags="-ldl" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 +$as_echo_n "checking for dlopen in -lsvld... " >&6; } +if ${ac_cv_lib_svld_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsvld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_svld_dlopen=yes +else + ac_cv_lib_svld_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 +$as_echo "$ac_cv_lib_svld_dlopen" >&6; } +if test "x$ac_cv_lib_svld_dlopen" = xyes; then : + svn_sqlite_dso_ldflags="-lsvld" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 +$as_echo_n "checking for dld_link in -ldld... " >&6; } +if ${ac_cv_lib_dld_dld_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dld_link (); +int +main () +{ +return dld_link (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_dld_link=yes +else + ac_cv_lib_dld_dld_link=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 +$as_echo "$ac_cv_lib_dld_dld_link" >&6; } +if test "x$ac_cv_lib_dld_dld_link" = xyes; then : + svn_sqlite_dso_ldflags="-ldld" +fi + + +fi + + +fi + + +fi + + +fi + + +fi + + ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking additional libraries for sqlite" >&5 +$as_echo_n "checking additional libraries for sqlite... " >&6; } + if test -n "$svn_sqlite_dso_ldflags"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${svn_sqlite_dso_ldflags}" >&5 +$as_echo "${svn_sqlite_dso_ldflags}" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } + fi + + +$as_echo "#define SVN_SQLITE_INLINE 1" >>confdefs.h + + SVN_SQLITE_INCLUDES="-I`dirname $sqlite_amalg`" + if test -n "$svn_sqlite_dso_ldflags"; then + SVN_SQLITE_LIBS="$svn_sqlite_dso_ldflags -lpthread" + else + SVN_SQLITE_LIBS="-lpthread" + fi + svn_lib_sqlite="yes" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported amalgamation SQLite version" >&5 +$as_echo "unsupported amalgamation SQLite version" >&6; } +fi +rm -f conftest* + + fi + + + if test -z "$svn_lib_sqlite"; then + + if test -z ""; then + sqlite_dir="" + sqlite_include="sqlite3.h" + else + sqlite_dir="" + sqlite_include="/include/sqlite3.h" + fi + + save_CPPFLAGS="$CPPFLAGS" + save_LDFLAGS="$LDFLAGS" + + if test ! -z ""; then + CPPFLAGS="$CPPFLAGS -I$sqlite_dir/include" + LDFLAGS="$LDFLAGS -L$sqlite_dir/lib" + fi + + ac_fn_c_check_header_mongrel "$LINENO" "sqlite3.h" "ac_cv_header_sqlite3_h" "$ac_includes_default" +if test "x$ac_cv_header_sqlite3_h" = xyes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite library version (via header)" >&5 +$as_echo_n "checking sqlite library version (via header)... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include "$sqlite_include" +#if SQLITE_VERSION_NUMBER >= $sqlite_min_ver_num +SQLITE_VERSION_OKAY +#endif +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "SQLITE_VERSION_OKAY" >/dev/null 2>&1; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: okay" >&5 +$as_echo "okay" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_close in -lsqlite3" >&5 +$as_echo_n "checking for sqlite3_close in -lsqlite3... " >&6; } +if ${ac_cv_lib_sqlite3_sqlite3_close+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsqlite3 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char sqlite3_close (); +int +main () +{ +return sqlite3_close (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_sqlite3_sqlite3_close=yes +else + ac_cv_lib_sqlite3_sqlite3_close=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sqlite3_sqlite3_close" >&5 +$as_echo "$ac_cv_lib_sqlite3_sqlite3_close" >&6; } +if test "x$ac_cv_lib_sqlite3_sqlite3_close" = xyes; then : + + svn_lib_sqlite="yes" + if test -z "$sqlite_dir" -o ! -d "$sqlite_dir"; then + SVN_SQLITE_LIBS="-lsqlite3" + else + SVN_SQLITE_INCLUDES="-I$sqlite_dir/include" + SVN_SQLITE_LIBS="` + input_flags="-L$sqlite_dir/lib -lsqlite3" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + fi + +fi + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported SQLite version" >&5 +$as_echo "unsupported SQLite version" >&6; } +fi +rm -f conftest* + + +fi + + + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + + fi + + if test -z "$svn_lib_sqlite"; then + + if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite library version (via pkg-config)" >&5 +$as_echo_n "checking sqlite library version (via pkg-config)... " >&6; } + sqlite_version=`$PKG_CONFIG $SQLITE_PKGNAME --modversion --silence-errors` + + if test -n "$sqlite_version"; then + + version_string="$sqlite_version" + + major=`expr $version_string : '\([0-9]*\)'` + minor=`expr $version_string : '[0-9]*\.\([0-9]*\)'` + micro=`expr $version_string : '[0-9]*\.[0-9]*\.\([0-9]*\)'` + if test -z "$micro"; then + micro=0 + fi + sqlite_ver_num=`expr $major \* 1000000 \ + \+ $minor \* 1000 \ + \+ $micro` + + + if test "$sqlite_ver_num" -ge "$sqlite_min_ver_num"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $sqlite_version" >&5 +$as_echo "$sqlite_version" >&6; } + svn_lib_sqlite="yes" + SVN_SQLITE_INCLUDES="`$PKG_CONFIG $SQLITE_PKGNAME --cflags`" + SVN_SQLITE_LIBS="`$PKG_CONFIG $SQLITE_PKGNAME --libs`" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none or unsupported $sqlite_version" >&5 +$as_echo "none or unsupported $sqlite_version" >&6; } + fi + fi + fi + + if test -z "$svn_lib_sqlite"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + + fi + + if test -z "$svn_lib_sqlite"; then + + echo "" + echo "An appropriate version of sqlite could not be found. We recommmend" + echo "${SQLITE_RECOMMENDED_VER}, but require at least ${SQLITE_MINIMUM_VER}." + echo "Please either install a newer sqlite on this system" + echo "" + echo "or" + echo "" + echo "get the sqlite ${SQLITE_RECOMMENDED_VER} amalgamation from:" + echo " ${SQLITE_URL}" + echo "unpack the archive using unzip and rename the resulting" + echo "directory to:" + echo "$abs_srcdir/sqlite-amalgamation" + echo "" + as_fn_error $? "Subversion requires SQLite" "$LINENO" 5 + + fi + +fi + + + + + + +# Check whether --enable-sqlite-compatibility-version was given. +if test "${enable_sqlite_compatibility_version+set}" = set; then : + enableval=$enable_sqlite_compatibility_version; sqlite_compat_ver=$enableval +else + sqlite_compat_ver=no +fi + + +if test -n "$sqlite_compat_ver" && test "$sqlite_compat_ver" != no; then + + version_string="$sqlite_compat_ver" + + major=`expr $version_string : '\([0-9]*\)'` + minor=`expr $version_string : '[0-9]*\.\([0-9]*\)'` + micro=`expr $version_string : '[0-9]*\.[0-9]*\.\([0-9]*\)'` + if test -z "$micro"; then + micro=0 + fi + sqlite_compat_ver_num=`expr $major \* 1000000 \ + \+ $minor \* 1000 \ + \+ $micro` + + CFLAGS="-DSVN_SQLITE_MIN_VERSION='\"$sqlite_compat_ver\"' $CFLAGS" + CFLAGS="-DSVN_SQLITE_MIN_VERSION_NUMBER=$sqlite_compat_ver_num $CFLAGS" +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the compiler provides atomic builtins" >&5 +$as_echo_n "checking whether the compiler provides atomic builtins... " >&6; } +if ${svn_cv_atomic_builtins+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + svn_cv_atomic_builtins=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + int main() + { + unsigned long long val = 1010, tmp, *mem = &val; + + if (__sync_fetch_and_add(&val, 1010) != 1010 || val != 2020) + return 1; + + tmp = val; + + if (__sync_fetch_and_sub(mem, 1010) != tmp || val != 1010) + return 1; + + if (__sync_sub_and_fetch(&val, 1010) != 0 || val != 0) + return 1; + + tmp = 3030; + + if (__sync_val_compare_and_swap(mem, 0, tmp) != 0 || val != tmp) + return 1; + + if (__sync_lock_test_and_set(&val, 4040) != 3030) + return 1; + + mem = &tmp; + + if (__sync_val_compare_and_swap(&mem, &tmp, &val) != &tmp) + return 1; + + __sync_synchronize(); + + if (mem != &val) + return 1; + + return 0; + } +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + svn_cv_atomic_builtins=yes +else + svn_cv_atomic_builtins=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_atomic_builtins" >&5 +$as_echo "$svn_cv_atomic_builtins" >&6; } + + +if test "$svn_cv_atomic_builtins" = "yes"; then + +$as_echo "#define SVN_HAS_ATOMIC_BUILTINS 1" >>confdefs.h + +fi + + +if test "${bindir}" = '${exec_prefix}/bin'; then + if test "${exec_prefix}" = "NONE"; then + if test "${prefix}" = "NONE"; then + SVN_BINDIR="${ac_default_prefix}/bin" + else + SVN_BINDIR="${prefix}/bin" + fi + else + SVN_BINDIR="${exec_prefix}/bin" + fi +else + SVN_BINDIR="${bindir}" +fi + +SVN_BINDIR="`eval echo ${SVN_BINDIR}`" + + + +cat >>confdefs.h <<_ACEOF +#define SVN_BINDIR "${SVN_BINDIR}" +_ACEOF + + +localedir='${datadir}/locale' + + +if test "${datadir}" = '${prefix}/share' && test "${prefix}" = "NONE"; then + exp_localedir='${ac_default_prefix}/share/locale' +else + exp_localedir=$localedir +fi + +svn_last= +svn_cur=""${exp_localedir}"" +while test "x${svn_cur}" != "x${svn_last}"; +do + svn_last="${svn_cur}" + svn_cur=`eval "echo ${svn_cur}"` +done +svn_localedir="${svn_cur}" + + +cat >>confdefs.h <<_ACEOF +#define SVN_LOCALE_DIR "${svn_localedir}" +_ACEOF + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: configuring libtool now" >&5 +$as_echo "$as_me: configuring libtool now" >&6;} +case `pwd` in + *\ * | *\ *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5 +$as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;; +esac + + + +macro_version='2.4.2' +macro_revision='1.3337' + + + + + + + + + + + + + +ltmain="$ac_aux_dir/ltmain.sh" + +# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5 +$as_echo_n "checking how to print strings... " >&6; } +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "" +} + +case "$ECHO" in + printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5 +$as_echo "printf" >&6; } ;; + print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5 +$as_echo "print -r" >&6; } ;; + *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5 +$as_echo "cat" >&6; } ;; +esac + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 +$as_echo_n "checking for a sed that does not truncate output... " >&6; } +if ${ac_cv_path_SED+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for ac_i in 1 2 3 4 5 6 7; do + ac_script="$ac_script$as_nl$ac_script" + done + echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed + { ac_script=; unset ac_script;} + if test -z "$SED"; then + ac_path_SED_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_SED" || continue +# Check for GNU ac_path_SED and select it if it is found. + # Check for GNU $ac_path_SED +case `"$ac_path_SED" --version 2>&1` in +*GNU*) + ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo '' >> "conftest.nl" + "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_SED_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_SED="$ac_path_SED" + ac_path_SED_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_SED_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_SED"; then + as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 + fi +else + ac_cv_path_SED=$SED +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 +$as_echo "$ac_cv_path_SED" >&6; } + SED="$ac_cv_path_SED" + rm -f conftest.sed + +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5 +$as_echo_n "checking for fgrep... " >&6; } +if ${ac_cv_path_FGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1 + then ac_cv_path_FGREP="$GREP -F" + else + if test -z "$FGREP"; then + ac_path_FGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in fgrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_FGREP" || continue +# Check for GNU ac_path_FGREP and select it if it is found. + # Check for GNU $ac_path_FGREP +case `"$ac_path_FGREP" --version 2>&1` in +*GNU*) + ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'FGREP' >> "conftest.nl" + "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_FGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_FGREP="$ac_path_FGREP" + ac_path_FGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_FGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_FGREP"; then + as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_FGREP=$FGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5 +$as_echo "$ac_cv_path_FGREP" >&6; } + FGREP="$ac_cv_path_FGREP" + + +test -z "$GREP" && GREP=grep + + + + + + + + + + + + + + + + + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${lt_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${lt_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5 +$as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; } +if ${lt_cv_path_NM+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM="$NM" +else + lt_nm_to_check="${ac_tool_prefix}nm" + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + tmp_nm="$ac_dir/$lt_tmp_nm" + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the `sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in + */dev/null* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS="$lt_save_ifs" + done + : ${lt_cv_path_NM=no} +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5 +$as_echo "$lt_cv_path_NM" >&6; } +if test "$lt_cv_path_NM" != "no"; then + NM="$lt_cv_path_NM" +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + if test -n "$ac_tool_prefix"; then + for ac_prog in dumpbin "link -dump" + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DUMPBIN"; then + ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DUMPBIN=$ac_cv_prog_DUMPBIN +if test -n "$DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5 +$as_echo "$DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$DUMPBIN" && break + done +fi +if test -z "$DUMPBIN"; then + ac_ct_DUMPBIN=$DUMPBIN + for ac_prog in dumpbin "link -dump" +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DUMPBIN"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN +if test -n "$ac_ct_DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5 +$as_echo "$ac_ct_DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_DUMPBIN" && break +done + + if test "x$ac_ct_DUMPBIN" = x; then + DUMPBIN=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DUMPBIN=$ac_ct_DUMPBIN + fi +fi + + case `$DUMPBIN -symbols /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols" + ;; + *) + DUMPBIN=: + ;; + esac + fi + + if test "$DUMPBIN" != ":"; then + NM="$DUMPBIN" + fi +fi +test -z "$NM" && NM=nm + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5 +$as_echo_n "checking the name lister ($NM) interface... " >&6; } +if ${lt_cv_nm_interface+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: output\"" >&5) + cat conftest.out >&5 + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5 +$as_echo "$lt_cv_nm_interface" >&6; } + +# find the maximum length of command line arguments +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5 +$as_echo_n "checking the maximum length of command line arguments... " >&6; } +if ${lt_cv_sys_max_cmd_len+:} false; then : + $as_echo_n "(cached) " >&6 +else + i=0 + teststring="ABCD" + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + netbsd* | freebsd* | openbsd* | darwin* | dragonfly*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[ ]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8 ; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test "X"`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test $i != 17 # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac + +fi + +if test -n $lt_cv_sys_max_cmd_len ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5 +$as_echo "$lt_cv_sys_max_cmd_len" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } +fi +max_cmd_len=$lt_cv_sys_max_cmd_len + + + + + + +: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the shell understands some XSI constructs" >&5 +$as_echo_n "checking whether the shell understands some XSI constructs... " >&6; } +# Try some XSI features +xsi_shell=no +( _lt_dummy="a/b/c" + test "${_lt_dummy##*/},${_lt_dummy%/*},${_lt_dummy#??}"${_lt_dummy%"$_lt_dummy"}, \ + = c,a/b,b/c, \ + && eval 'test $(( 1 + 1 )) -eq 2 \ + && test "${#_lt_dummy}" -eq 5' ) >/dev/null 2>&1 \ + && xsi_shell=yes +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $xsi_shell" >&5 +$as_echo "$xsi_shell" >&6; } + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the shell understands \"+=\"" >&5 +$as_echo_n "checking whether the shell understands \"+=\"... " >&6; } +lt_shell_append=no +( foo=bar; set foo baz; eval "$1+=\$2" && test "$foo" = barbaz ) \ + >/dev/null 2>&1 \ + && lt_shell_append=yes +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_shell_append" >&5 +$as_echo "$lt_shell_append" >&6; } + + +if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi + + + + + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5 +$as_echo_n "checking how to convert $build file names to $host format... " >&6; } +if ${lt_cv_to_host_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac + +fi + +to_host_file_cmd=$lt_cv_to_host_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5 +$as_echo "$lt_cv_to_host_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5 +$as_echo_n "checking how to convert $build file names to toolchain format... " >&6; } +if ${lt_cv_to_tool_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + #assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac + +fi + +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5 +$as_echo "$lt_cv_to_tool_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5 +$as_echo_n "checking for $LD option to reload object files... " >&6; } +if ${lt_cv_ld_reload_flag+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_reload_flag='-r' +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5 +$as_echo "$lt_cv_ld_reload_flag" >&6; } +reload_flag=$lt_cv_ld_reload_flag +case $reload_flag in +"" | " "*) ;; +*) reload_flag=" $reload_flag" ;; +esac +reload_cmds='$LD$reload_flag -o $output$reload_objs' +case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + if test "$GCC" != yes; then + reload_cmds=false + fi + ;; + darwin*) + if test "$GCC" = yes; then + reload_cmds='$LTCC $LTCFLAGS -nostdlib ${wl}-r -o $output$reload_objs' + else + reload_cmds='$LD$reload_flag -o $output$reload_objs' + fi + ;; +esac + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args. +set dummy ${ac_tool_prefix}objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OBJDUMP"; then + ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OBJDUMP=$ac_cv_prog_OBJDUMP +if test -n "$OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5 +$as_echo "$OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OBJDUMP"; then + ac_ct_OBJDUMP=$OBJDUMP + # Extract the first word of "objdump", so it can be a program name with args. +set dummy objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OBJDUMP"; then + ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OBJDUMP="objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP +if test -n "$ac_ct_OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5 +$as_echo "$ac_ct_OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OBJDUMP" = x; then + OBJDUMP="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OBJDUMP=$ac_ct_OBJDUMP + fi +else + OBJDUMP="$ac_cv_prog_OBJDUMP" +fi + +test -z "$OBJDUMP" && OBJDUMP=objdump + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5 +$as_echo_n "checking how to recognize dependent libraries... " >&6; } +if ${lt_cv_deplibs_check_method+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# `unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# which responds to the $file_magic_cmd with a given extended regex. +# If you have `file' or equivalent on your system and you're not sure +# whether `pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[4-9]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[45]*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + # func_win32_libid assumes BSD nm, so disallow it if using MS dumpbin. + if ( test "$lt_cv_nm_interface" = "BSD nm" && file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]' + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[3-9]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5 +$as_echo "$lt_cv_deplibs_check_method" >&6; } + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + + + + + + + + + + + + + + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args. +set dummy ${ac_tool_prefix}dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DLLTOOL"; then + ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DLLTOOL=$ac_cv_prog_DLLTOOL +if test -n "$DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5 +$as_echo "$DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DLLTOOL"; then + ac_ct_DLLTOOL=$DLLTOOL + # Extract the first word of "dlltool", so it can be a program name with args. +set dummy dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DLLTOOL"; then + ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DLLTOOL="dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL +if test -n "$ac_ct_DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5 +$as_echo "$ac_ct_DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DLLTOOL" = x; then + DLLTOOL="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DLLTOOL=$ac_ct_DLLTOOL + fi +else + DLLTOOL="$ac_cv_prog_DLLTOOL" +fi + +test -z "$DLLTOOL" && DLLTOOL=dlltool + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5 +$as_echo_n "checking how to associate runtime and link libraries... " >&6; } +if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh + # decide which to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd="$ECHO" + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5 +$as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; } +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + + + + + + + +if test -n "$ac_tool_prefix"; then + for ac_prog in ar + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +$as_echo "$AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AR" && break + done +fi +if test -z "$AR"; then + ac_ct_AR=$AR + for ac_prog in ar +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 +$as_echo "$ac_ct_AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_AR" && break +done + + if test "x$ac_ct_AR" = x; then + AR="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +fi + +: ${AR=ar} +: ${AR_FLAGS=cru} + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5 +$as_echo_n "checking for archiver @FILE support... " >&6; } +if ${lt_cv_ar_at_file+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ar_at_file=no + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test "$ac_status" -eq 0; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test "$ac_status" -ne 0; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5 +$as_echo "$lt_cv_ar_at_file" >&6; } + +if test "x$lt_cv_ar_at_file" = xno; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +test -z "$STRIP" && STRIP=: + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + +test -z "$RANLIB" && RANLIB=: + + + + + + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac + + + + + + + + + + + + + + + + + + + + + +for ac_prog in gawk mawk nawk awk +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AWK+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AWK="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AWK=$ac_cv_prog_AWK +if test -n "$AWK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 +$as_echo "$AWK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AWK" && break +done + + + + + + + + + + + + + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# Check for command to grab the raw symbol name followed by C symbol from nm. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5 +$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; } +if ${lt_cv_sys_global_symbol_pipe+:} false; then : + $as_echo_n "(cached) " >&6 +else + +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[BCDEGRST]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([_A-Za-z][_A-Za-z0-9]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[BCDT]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[ABCDGISTW]' + ;; +hpux*) + if test "$host_cpu" = ia64; then + symcode='[ABCDEGRST]' + fi + ;; +irix* | nonstopux*) + symcode='[BCDEGRST]' + ;; +osf*) + symcode='[BCDEGQRST]' + ;; +solaris*) + symcode='[BDRT]' + ;; +sco3.2v5*) + symcode='[DT]' + ;; +sysv4.2uw2*) + symcode='[DT]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[ABDT]' + ;; +sysv4) + symcode='[DFNSTU]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[ABCDGIRSTW]' ;; +esac + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\)[ ]*$/ {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/ {\"\2\", (void *) \&\2},/p'" +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n -e 's/^: \([^ ]*\)[ ]*$/ {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([^ ]*\) \(lib[^ ]*\)$/ {\"\2\", (void *) \&\2},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/ {\"lib\2\", (void *) \&\2},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function + # and D for any global variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK '"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=0}; \$ 0~/\(\).*\|/{f=1}; {printf f ? \"T \" : \"D \"};"\ +" {split(\$ 0, a, /\||\r/); split(a[2], s)};"\ +" s[1]~/^[@?]/{print s[1], s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print t[1], substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + # Now try to grab the symbols. + nlist=conftest.nm + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist\""; } >&5 + (eval $NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE) +/* DATA imports from DLLs on WIN32 con't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined(__osf__) +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (void *) \&\2},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS="conftstm.$ac_objext" + CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s conftest${ac_exeext}; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&5 + fi + else + echo "cannot find nm_test_var in $nlist" >&5 + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 + fi + else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test "$pipe_works" = yes; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done + +fi + +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5 +$as_echo "failed" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5 +$as_echo "ok" >&6; } +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5 +$as_echo_n "checking for sysroot... " >&6; } + +# Check whether --with-sysroot was given. +if test "${with_sysroot+set}" = set; then : + withval=$with_sysroot; +else + with_sysroot=no +fi + + +lt_sysroot= +case ${with_sysroot} in #( + yes) + if test "$GCC" = yes; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${with_sysroot}" >&5 +$as_echo "${with_sysroot}" >&6; } + as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5 + ;; +esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5 +$as_echo "${lt_sysroot:-no}" >&6; } + + + + + +# Check whether --enable-libtool-lock was given. +if test "${enable_libtool_lock+set}" = set; then : + enableval=$enable_libtool_lock; +fi + +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE="32" + ;; + *ELF-64*) + HPUX_IA64_MODE="64" + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out which ABI we are using. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + if test "$lt_cv_prog_gnu_ld" = yes; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_i386" + ;; + ppc64-*linux*|powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + ppc*-*linux*|powerpc*-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -belf" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5 +$as_echo_n "checking whether the C compiler needs -belf... " >&6; } +if ${lt_cv_cc_needs_belf+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_cc_needs_belf=yes +else + lt_cv_cc_needs_belf=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5 +$as_echo "$lt_cv_cc_needs_belf" >&6; } + if test x"$lt_cv_cc_needs_belf" != x"yes"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS="$SAVE_CFLAGS" + fi + ;; +*-*solaris*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD="${LD-ld}_sol2" + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks="$enable_libtool_lock" + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args. +set dummy ${ac_tool_prefix}mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$MANIFEST_TOOL"; then + ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL +if test -n "$MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5 +$as_echo "$MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_MANIFEST_TOOL"; then + ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL + # Extract the first word of "mt", so it can be a program name with args. +set dummy mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_MANIFEST_TOOL"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL +if test -n "$ac_ct_MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5 +$as_echo "$ac_ct_MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_MANIFEST_TOOL" = x; then + MANIFEST_TOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL + fi +else + MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL" +fi + +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5 +$as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; } +if ${lt_cv_path_mainfest_tool+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5 + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&5 + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5 +$as_echo "$lt_cv_path_mainfest_tool" >&6; } +if test "x$lt_cv_path_mainfest_tool" != xyes; then + MANIFEST_TOOL=: +fi + + + + + + + case $host_os in + rhapsody* | darwin*) + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. +set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DSYMUTIL"; then + ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DSYMUTIL=$ac_cv_prog_DSYMUTIL +if test -n "$DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5 +$as_echo "$DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DSYMUTIL"; then + ac_ct_DSYMUTIL=$DSYMUTIL + # Extract the first word of "dsymutil", so it can be a program name with args. +set dummy dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DSYMUTIL"; then + ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL +if test -n "$ac_ct_DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5 +$as_echo "$ac_ct_DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DSYMUTIL" = x; then + DSYMUTIL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DSYMUTIL=$ac_ct_DSYMUTIL + fi +else + DSYMUTIL="$ac_cv_prog_DSYMUTIL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. +set dummy ${ac_tool_prefix}nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NMEDIT"; then + ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +NMEDIT=$ac_cv_prog_NMEDIT +if test -n "$NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5 +$as_echo "$NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_NMEDIT"; then + ac_ct_NMEDIT=$NMEDIT + # Extract the first word of "nmedit", so it can be a program name with args. +set dummy nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_NMEDIT"; then + ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_NMEDIT="nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT +if test -n "$ac_ct_NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5 +$as_echo "$ac_ct_NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_NMEDIT" = x; then + NMEDIT=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + NMEDIT=$ac_ct_NMEDIT + fi +else + NMEDIT="$ac_cv_prog_NMEDIT" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args. +set dummy ${ac_tool_prefix}lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$LIPO"; then + ac_cv_prog_LIPO="$LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_LIPO="${ac_tool_prefix}lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +LIPO=$ac_cv_prog_LIPO +if test -n "$LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5 +$as_echo "$LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_LIPO"; then + ac_ct_LIPO=$LIPO + # Extract the first word of "lipo", so it can be a program name with args. +set dummy lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_LIPO"; then + ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_LIPO="lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO +if test -n "$ac_ct_LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5 +$as_echo "$ac_ct_LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_LIPO" = x; then + LIPO=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + LIPO=$ac_ct_LIPO + fi +else + LIPO="$ac_cv_prog_LIPO" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL"; then + ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL="${ac_tool_prefix}otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL=$ac_cv_prog_OTOOL +if test -n "$OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5 +$as_echo "$OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL"; then + ac_ct_OTOOL=$OTOOL + # Extract the first word of "otool", so it can be a program name with args. +set dummy otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL"; then + ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL="otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL +if test -n "$ac_ct_OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5 +$as_echo "$ac_ct_OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL" = x; then + OTOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL=$ac_ct_OTOOL + fi +else + OTOOL="$ac_cv_prog_OTOOL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL64"; then + ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL64=$ac_cv_prog_OTOOL64 +if test -n "$OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5 +$as_echo "$OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL64"; then + ac_ct_OTOOL64=$OTOOL64 + # Extract the first word of "otool64", so it can be a program name with args. +set dummy otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL64"; then + ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL64="otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64 +if test -n "$ac_ct_OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5 +$as_echo "$ac_ct_OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL64" = x; then + OTOOL64=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL64=$ac_ct_OTOOL64 + fi +else + OTOOL64="$ac_cv_prog_OTOOL64" +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5 +$as_echo_n "checking for -single_module linker flag... " >&6; } +if ${lt_cv_apple_cc_single_mod+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_apple_cc_single_mod=no + if test -z "${LT_MULTI_MODULE}"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&5 + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test $_lt_result -eq 0; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&5 + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5 +$as_echo "$lt_cv_apple_cc_single_mod" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5 +$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; } +if ${lt_cv_ld_exported_symbols_list+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_ld_exported_symbols_list=yes +else + lt_cv_ld_exported_symbols_list=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5 +$as_echo "$lt_cv_ld_exported_symbols_list" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5 +$as_echo_n "checking for -force_load linker flag... " >&6; } +if ${lt_cv_ld_force_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 + echo "$AR cru libconftest.a conftest.o" >&5 + $AR cru libconftest.a conftest.o 2>&5 + echo "$RANLIB libconftest.a" >&5 + $RANLIB libconftest.a 2>&5 + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&5 + elif test -f conftest && test $_lt_result -eq 0 && $GREP forced_load conftest >/dev/null 2>&1 ; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&5 + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5 +$as_echo "$lt_cv_ld_force_load" >&6; } + case $host_os in + rhapsody* | darwin1.[012]) + _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[91]*) + _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;; + 10.[012]*) + _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test "$lt_cv_apple_cc_single_mod" = "yes"; then + _lt_dar_single_mod='$single_module' + fi + if test "$lt_cv_ld_exported_symbols_list" = "yes"; then + _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/${libname}-symbols.expsym ${lib}' + fi + if test "$DSYMUTIL" != ":" && test "$lt_cv_ld_force_load" = "no"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac + +for ac_header in dlfcn.h +do : + ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default +" +if test "x$ac_cv_header_dlfcn_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_DLFCN_H 1 +_ACEOF + +fi + +done + + + +func_stripname_cnf () +{ + case ${2} in + .*) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%\\\\${2}\$%%"`;; + *) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%${2}\$%%"`;; + esac +} # func_stripname_cnf + + + + + +# Set options + + + + enable_dlopen=no + + + enable_win32_dll=no + + + # Check whether --enable-shared was given. +if test "${enable_shared+set}" = set; then : + enableval=$enable_shared; p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac +else + enable_shared=yes +fi + + + + + + + + + + # Check whether --enable-static was given. +if test "${enable_static+set}" = set; then : + enableval=$enable_static; p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac +else + enable_static=yes +fi + + + + + + + + + + +# Check whether --with-pic was given. +if test "${with_pic+set}" = set; then : + withval=$with_pic; lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for lt_pkg in $withval; do + IFS="$lt_save_ifs" + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac +else + pic_mode=default +fi + + +test -z "$pic_mode" && pic_mode=default + + + + + + + + # Check whether --enable-fast-install was given. +if test "${enable_fast_install+set}" = set; then : + enableval=$enable_fast_install; p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac +else + enable_fast_install=yes +fi + + + + + + + + + + + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS="$ltmain" + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +test -z "$LN_S" && LN_S="ln -s" + + + + + + + + + + + + + + +if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5 +$as_echo_n "checking for objdir... " >&6; } +if ${lt_cv_objdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5 +$as_echo "$lt_cv_objdir" >&6; } +objdir=$lt_cv_objdir + + + + + +cat >>confdefs.h <<_ACEOF +#define LT_OBJDIR "$lt_cv_objdir/" +_ACEOF + + + + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a `.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld="$lt_cv_prog_gnu_ld" + +old_CC="$CC" +old_CFLAGS="$CFLAGS" + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` + + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5 +$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD="$MAGIC_CMD" + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/${ac_tool_prefix}file; then + lt_cv_path_MAGIC_CMD="$ac_dir/${ac_tool_prefix}file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD="$lt_cv_path_MAGIC_CMD" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS="$lt_save_ifs" + MAGIC_CMD="$lt_save_MAGIC_CMD" + ;; +esac +fi + +MAGIC_CMD="$lt_cv_path_MAGIC_CMD" +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + + +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5 +$as_echo_n "checking for file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD="$MAGIC_CMD" + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/file; then + lt_cv_path_MAGIC_CMD="$ac_dir/file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD="$lt_cv_path_MAGIC_CMD" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS="$lt_save_ifs" + MAGIC_CMD="$lt_save_MAGIC_CMD" + ;; +esac +fi + +MAGIC_CMD="$lt_cv_path_MAGIC_CMD" +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + MAGIC_CMD=: + fi +fi + + fi + ;; +esac + +# Use C for the default configuration in the libtool script + +lt_save_CC="$CC" +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +objext=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* + + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + +lt_prog_compiler_no_builtin_flag= + +if test "$GCC" = yes; then + case $cc_basename in + nvcc*) + lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;; + *) + lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 +$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } +if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_rtti_exceptions=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="-fno-rtti -fno-exceptions" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_rtti_exceptions=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 +$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; } + +if test x"$lt_cv_prog_compiler_rtti_exceptions" = xyes; then + lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" +else + : +fi + +fi + + + + + + + lt_prog_compiler_wl= +lt_prog_compiler_pic= +lt_prog_compiler_static= + + + if test "$GCC" = yes; then + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_static='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + lt_prog_compiler_pic='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic='-DDLL_EXPORT' + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + lt_prog_compiler_static= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + ;; + + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + lt_prog_compiler_can_build_shared=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic=-Kconform_pic + fi + ;; + + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + lt_prog_compiler_wl='-Xlinker ' + if test -n "$lt_prog_compiler_pic"; then + lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + lt_prog_compiler_wl='-Wl,' + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + else + lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic='-DDLL_EXPORT' + ;; + + hpux9* | hpux10* | hpux11*) + lt_prog_compiler_wl='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + lt_prog_compiler_static='${wl}-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + lt_prog_compiler_wl='-Wl,' + # PIC (with -KPIC) is the default. + lt_prog_compiler_static='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu) + case $cc_basename in + # old Intel for x86_64 which still supported -KPIC. + ecc*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='--shared' + lt_prog_compiler_static='--static' + ;; + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + ccc*) + lt_prog_compiler_wl='-Wl,' + # All Alpha code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-qpic' + lt_prog_compiler_static='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='' + ;; + *Sun\ F* | *Sun*Fortran*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Wl,' + ;; + *Intel*\ [CF]*Compiler*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + *Portland\ Group*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + lt_prog_compiler_wl='-Wl,' + # All OSF/1 code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + + rdos*) + lt_prog_compiler_static='-non_shared' + ;; + + solaris*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + lt_prog_compiler_wl='-Qoption ld ';; + *) + lt_prog_compiler_wl='-Wl,';; + esac + ;; + + sunos4*) + lt_prog_compiler_wl='-Qoption ld ' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec ;then + lt_prog_compiler_pic='-Kconform_pic' + lt_prog_compiler_static='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + unicos*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_can_build_shared=no + ;; + + uts4*) + lt_prog_compiler_pic='-pic' + lt_prog_compiler_static='-Bstatic' + ;; + + *) + lt_prog_compiler_can_build_shared=no + ;; + esac + fi + +case $host_os in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic= + ;; + *) + lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } +if ${lt_cv_prog_compiler_pic+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic=$lt_prog_compiler_pic +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 +$as_echo "$lt_cv_prog_compiler_pic" >&6; } +lt_prog_compiler_pic=$lt_cv_prog_compiler_pic + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } +if ${lt_cv_prog_compiler_pic_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic -DPIC" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works" >&6; } + +if test x"$lt_cv_prog_compiler_pic_works" = xyes; then + case $lt_prog_compiler_pic in + "" | " "*) ;; + *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; + esac +else + lt_prog_compiler_pic= + lt_prog_compiler_can_build_shared=no +fi + +fi + + + + + + + + + + + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if ${lt_cv_prog_compiler_static_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works=yes + fi + else + lt_cv_prog_compiler_static_works=yes + fi + fi + $RM -r conftest* + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 +$as_echo "$lt_cv_prog_compiler_static_works" >&6; } + +if test x"$lt_cv_prog_compiler_static_works" = xyes; then + : +else + lt_prog_compiler_static= +fi + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + +hard_links="nottested" +if test "$lt_cv_prog_compiler_c_o" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test "$hard_links" = no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + runpath_var= + allow_undefined_flag= + always_export_symbols=no + archive_cmds= + archive_expsym_cmds= + compiler_needs_object=no + enable_shared_with_static_runtimes=no + export_dynamic_flag_spec= + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + hardcode_automatic=no + hardcode_direct=no + hardcode_direct_absolute=no + hardcode_libdir_flag_spec= + hardcode_libdir_separator= + hardcode_minus_L=no + hardcode_shlibpath_var=unsupported + inherit_rpath=no + link_all_deplibs=unknown + module_cmds= + module_expsym_cmds= + old_archive_from_new_cmds= + old_archive_from_expsyms_cmds= + thread_safe_flag_spec= + whole_archive_flag_spec= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + include_expsyms= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ` (' and `)$', so one must not match beginning or + # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', + # as well as any symbol that contains `d'. + exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd*) + with_gnu_ld=no + ;; + esac + + ld_shlibs=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test "$with_gnu_ld" = yes; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;; + *\ \(GNU\ Binutils\)\ [3-9]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test "$lt_use_gnu_ld_interface" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + export_dynamic_flag_spec='${wl}--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + whole_archive_flag_spec= + fi + supports_anon_versioning=no + case `$LD -v 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + ld_shlibs=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + export_dynamic_flag_spec='${wl}--export-all-symbols' + allow_undefined_flag=unsupported + always_export_symbols=no + enable_shared_with_static_runtimes=yes + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs=no + fi + ;; + + haiku*) + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + link_all_deplibs=yes + ;; + + interix[3-9]*) + hardcode_direct=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + export_dynamic_flag_spec='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test "$host_os" = linux-dietlibc; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test "$tmp_diet" = no + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + whole_archive_flag_spec= + tmp_sharedflag='--shared' ;; + xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive' + compiler_needs_object=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + whole_archive_flag_spec='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive' + compiler_needs_object=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + + if test "x$supports_anon_versioning" = xyes; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive' + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test "x$supports_anon_versioning" = xyes; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + ld_shlibs=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + ;; + + sunos4*) + archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + + if test "$ld_shlibs" = no; then + runpath_var= + hardcode_libdir_flag_spec= + export_dynamic_flag_spec= + whole_archive_flag_spec= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + allow_undefined_flag=unsupported + always_export_symbols=yes + archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + # Also, AIX nm treats weak defined symbols like other global + # defined symbols, whereas GNU nm marks them as "W". + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds='' + hardcode_direct=yes + hardcode_direct_absolute=yes + hardcode_libdir_separator=':' + link_all_deplibs=yes + file_list_spec='${wl}-f,' + + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + ;; + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + export_dynamic_flag_spec='${wl}-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + if test "${lt_cv_aix_libpath+set}" = set; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_="/usr/lib:/lib" + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' + allow_undefined_flag="-z nodefs" + archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + if test "${lt_cv_aix_libpath+set}" = set; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_="/usr/lib:/lib" + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag=' ${wl}-bernotok' + allow_undefined_flag=' ${wl}-berok' + if test "$with_gnu_ld" = yes; then + # We only use this code for GNU lds that support --whole-archive. + whole_archive_flag_spec='${wl}--whole-archive$convenience ${wl}--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec='$convenience' + fi + archive_cmds_need_lc=yes + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + bsdi[45]*) + export_dynamic_flag_spec=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + always_export_symbols=yes + file_list_spec='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=".dll" + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames=' + archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + sed -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp; + else + sed -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, )='true' + enable_shared_with_static_runtimes=yes + exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + old_postinstall_cmds='chmod 644 $oldlib' + postlink_cmds='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile="$lt_outputfile.exe" + lt_tool_outputfile="$lt_tool_outputfile.exe" + ;; + esac~ + if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=".dll" + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_from_new_cmds='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' + enable_shared_with_static_runtimes=yes + ;; + esac + ;; + + darwin* | rhapsody*) + + + archive_cmds_need_lc=no + hardcode_direct=no + hardcode_automatic=yes + hardcode_shlibpath_var=unsupported + if test "$lt_cv_ld_force_load" = "yes"; then + whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + + else + whole_archive_flag_spec='' + fi + link_all_deplibs=yes + allow_undefined_flag="$_lt_dar_allow_undefined" + case $cc_basename in + ifort*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test "$_lt_dar_can_shared" = "yes"; then + output_verbose_link_cmd=func_echo_all + archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + archive_expsym_cmds="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + module_expsym_cmds="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + + else + ld_shlibs=no + fi + + ;; + + dgux*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + hpux9*) + if test "$GCC" = yes; then + archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + fi + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + export_dynamic_flag_spec='${wl}-E' + ;; + + hpux10*) + if test "$GCC" = yes && test "$with_gnu_ld" = no; then + archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='${wl}-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + fi + ;; + + hpux11*) + if test "$GCC" = yes && test "$with_gnu_ld" = no; then + case $host_cpu in + hppa*64*) + archive_cmds='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + archive_cmds='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5 +$as_echo_n "checking if $CC understands -b... " >&6; } +if ${lt_cv_prog_compiler__b+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler__b=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -b" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler__b=yes + fi + else + lt_cv_prog_compiler__b=yes + fi + fi + $RM -r conftest* + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5 +$as_echo "$lt_cv_prog_compiler__b" >&6; } + +if test x"$lt_cv_prog_compiler__b" = xyes; then + archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' +else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' +fi + + ;; + esac + fi + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct=no + hardcode_shlibpath_var=no + ;; + *) + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test "$GCC" = yes; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5 +$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; } +if ${lt_cv_irix_exported_symbol+:} false; then : + $as_echo_n "(cached) " >&6 +else + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int foo (void) { return 0; } +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_irix_exported_symbol=yes +else + lt_cv_irix_exported_symbol=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS="$save_LDFLAGS" +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5 +$as_echo "$lt_cv_irix_exported_symbol" >&6; } + if test "$lt_cv_irix_exported_symbol" = yes; then + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib' + fi + else + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + inherit_rpath=yes + link_all_deplibs=yes + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + newsos6) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_shlibpath_var=no + ;; + + *nto* | *qnx*) + ;; + + openbsd*) + if test -f /usr/libexec/ld.so; then + hardcode_direct=yes + hardcode_shlibpath_var=no + hardcode_direct_absolute=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols' + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + export_dynamic_flag_spec='${wl}-E' + else + case $host_os in + openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-R$libdir' + ;; + *) + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + ;; + esac + fi + else + ld_shlibs=no + fi + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + archive_cmds='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~echo DATA >> $output_objdir/$libname.def~echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def' + old_archive_from_new_cmds='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def' + ;; + + osf3*) + if test "$GCC" = yes; then + allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test "$GCC" = yes; then + allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds='$CC -shared${allow_undefined_flag} $pic_flag $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib' + archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + archive_cmds_need_lc='no' + hardcode_libdir_separator=: + ;; + + solaris*) + no_undefined_flag=' -z defs' + if test "$GCC" = yes; then + wlarc='${wl}' + archive_cmds='$CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='${wl}' + archive_cmds='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_shlibpath_var=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. GCC discards it without `$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test "$GCC" = yes; then + whole_archive_flag_spec='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + else + whole_archive_flag_spec='-z allextract$convenience -z defaultextract' + fi + ;; + esac + link_all_deplibs=yes + ;; + + sunos4*) + if test "x$host_vendor" = xsequent; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + archive_cmds='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + sysv4) + case $host_vendor in + sni) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' + reload_cmds='$CC -r -o $output$reload_objs' + hardcode_direct=no + ;; + motorola) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var=no + ;; + + sysv4.3*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + export_dynamic_flag_spec='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ld_shlibs=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag='${wl}-z,text' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag='${wl}-z,text' + allow_undefined_flag='${wl}-z,nodefs' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='${wl}-R,$libdir' + hardcode_libdir_separator=':' + link_all_deplibs=yes + export_dynamic_flag_spec='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + *) + ld_shlibs=no + ;; + esac + + if test x$host_vendor = xsni; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + export_dynamic_flag_spec='${wl}-Blargedynsym' + ;; + esac + fi + fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5 +$as_echo "$ld_shlibs" >&6; } +test "$ld_shlibs" = no && can_build_shared=no + +with_gnu_ld=$with_gnu_ld + + + + + + + + + + + + + + + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $archive_cmds in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } +if ${lt_cv_archive_cmds_need_lc+:} false; then : + $as_echo_n "(cached) " >&6 +else + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl + pic_flag=$lt_prog_compiler_pic + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag + allow_undefined_flag= + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 + (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + then + lt_cv_archive_cmds_need_lc=no + else + lt_cv_archive_cmds_need_lc=yes + fi + allow_undefined_flag=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5 +$as_echo "$lt_cv_archive_cmds_need_lc" >&6; } + archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc + ;; + esac + fi + ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } + +if test "$GCC" = yes; then + case $host_os in + darwin*) lt_awk_arg="/^libraries:/,/LR/" ;; + *) lt_awk_arg="/^libraries:/" ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq="s,=\([A-Za-z]:\),\1,g" ;; + *) lt_sed_strip_eq="s,=/,/,g" ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary. + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path/$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir" + else + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS=" "; FS="/|\n";} { + lt_foo=""; + lt_count=0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo="/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[lt_foo]++; } + if (lt_freq[lt_foo] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's,/\([A-Za-z]:\),\1,g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix[4-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api" + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + library_names_spec='${libname}.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec="$LIB" + if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[23].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=yes + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[3-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + if ${lt_cv_shlibpath_overrides_runpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \ + LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\"" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : + lt_cv_shlibpath_overrides_runpath=yes +fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + +fi + + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Append ld.so.conf contents to the search path + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd*) + version_type=sunos + sys_lib_dlsearch_path_spec="/usr/lib" + need_lib_prefix=no + # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs. + case $host_os in + openbsd3.3 | openbsd3.3.*) need_version=yes ;; + *) need_version=no ;; + esac + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[89] | openbsd2.[89].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext_cmds=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=freebsd-elf + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test "$with_gnu_ld" = yes; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test "$dynamic_linker" = no && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then + sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec" +fi +if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then + sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec" +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action= +if test -n "$hardcode_libdir_flag_spec" || + test -n "$runpath_var" || + test "X$hardcode_automatic" = "Xyes" ; then + + # We can hardcode non-existent directories. + if test "$hardcode_direct" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_TAGVAR(hardcode_shlibpath_var, )" != no && + test "$hardcode_minus_L" != no; then + # Linking always hardcodes the temporary library directory. + hardcode_action=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action=unsupported +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5 +$as_echo "$hardcode_action" >&6; } + +if test "$hardcode_action" = relink || + test "$inherit_rpath" = yes; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + + + + + + if test "x$enable_dlopen" != xyes; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen="load_add_on" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen="LoadLibrary" + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen="dlopen" + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl" +else + + lt_cv_dlopen="dyld" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + +fi + + ;; + + *) + ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" +if test "x$ac_cv_func_shl_load" = xyes; then : + lt_cv_dlopen="shl_load" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld" +else + ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" +if test "x$ac_cv_func_dlopen" = xyes; then : + lt_cv_dlopen="dlopen" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 +$as_echo_n "checking for dlopen in -lsvld... " >&6; } +if ${ac_cv_lib_svld_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsvld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_svld_dlopen=yes +else + ac_cv_lib_svld_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 +$as_echo "$ac_cv_lib_svld_dlopen" >&6; } +if test "x$ac_cv_lib_svld_dlopen" = xyes; then : + lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 +$as_echo_n "checking for dld_link in -ldld... " >&6; } +if ${ac_cv_lib_dld_dld_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dld_link (); +int +main () +{ +return dld_link (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_dld_link=yes +else + ac_cv_lib_dld_dld_link=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 +$as_echo "$ac_cv_lib_dld_dld_link" >&6; } +if test "x$ac_cv_lib_dld_dld_link" = xyes; then : + lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld" +fi + + +fi + + +fi + + +fi + + +fi + + +fi + + ;; + esac + + if test "x$lt_cv_dlopen" != xno; then + enable_dlopen=yes + else + enable_dlopen=no + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS="$CPPFLAGS" + test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS="$LDFLAGS" + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS="$LIBS" + LIBS="$lt_cv_dlopen_libs $LIBS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5 +$as_echo_n "checking whether a program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + lt_cv_dlopen_self=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisbility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s conftest${ac_exeext} 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5 +$as_echo "$lt_cv_dlopen_self" >&6; } + + if test "x$lt_cv_dlopen_self" = xyes; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5 +$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self_static+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + lt_cv_dlopen_self_static=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisbility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s conftest${ac_exeext} 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self_static=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5 +$as_echo "$lt_cv_dlopen_self_static" >&6; } + fi + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi + + + + + + + + + + + + + + + + + +striplib= +old_striplib= +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5 +$as_echo_n "checking whether stripping libraries is possible... " >&6; } +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP" ; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; + esac +fi + + + + + + + + + + + + + # Report which library types will actually be built + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5 +$as_echo_n "checking if libtool supports shared libraries... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5 +$as_echo "$can_build_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5 +$as_echo_n "checking whether to build shared libraries... " >&6; } + test "$can_build_shared" = "no" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[4-9]*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; + esac + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5 +$as_echo "$enable_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5 +$as_echo_n "checking whether to build static libraries... " >&6; } + # Make sure either enable_shared or enable_static is yes. + test "$enable_shared" = yes || enable_static=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5 +$as_echo "$enable_static" >&6; } + + + + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC="$lt_save_CC" + + if test -n "$CXX" && ( test "X$CXX" != "Xno" && + ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) || + (test "X$CXX" != "Xg++"))) ; then + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 +$as_echo_n "checking how to run the C++ preprocessor... " >&6; } +if test -z "$CXXCPP"; then + if ${ac_cv_prog_CXXCPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CXXCPP needs to be expanded + for CXXCPP in "$CXX -E" "/lib/cpp" + do + ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CXXCPP=$CXXCPP + +fi + CXXCPP=$ac_cv_prog_CXXCPP +else + ac_cv_prog_CXXCPP=$CXXCPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 +$as_echo "$CXXCPP" >&6; } +ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +else + _lt_caught_CXX_error=yes +fi + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +archive_cmds_need_lc_CXX=no +allow_undefined_flag_CXX= +always_export_symbols_CXX=no +archive_expsym_cmds_CXX= +compiler_needs_object_CXX=no +export_dynamic_flag_spec_CXX= +hardcode_direct_CXX=no +hardcode_direct_absolute_CXX=no +hardcode_libdir_flag_spec_CXX= +hardcode_libdir_separator_CXX= +hardcode_minus_L_CXX=no +hardcode_shlibpath_var_CXX=unsupported +hardcode_automatic_CXX=no +inherit_rpath_CXX=no +module_cmds_CXX= +module_expsym_cmds_CXX= +link_all_deplibs_CXX=unknown +old_archive_cmds_CXX=$old_archive_cmds +reload_flag_CXX=$reload_flag +reload_cmds_CXX=$reload_cmds +no_undefined_flag_CXX= +whole_archive_flag_spec_CXX= +enable_shared_with_static_runtimes_CXX=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +objext_CXX=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test "$_lt_caught_CXX_error" != yes; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + + # save warnings/boilerplate of simple test code + ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* + + ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* + + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + compiler_CXX=$CC + for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` + + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test "$GXX" = yes; then + lt_prog_compiler_no_builtin_flag_CXX=' -fno-builtin' + else + lt_prog_compiler_no_builtin_flag_CXX= + fi + + if test "$GXX" = yes; then + # Set up default GNU C++ configuration + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${lt_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${lt_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + + + + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test "$with_gnu_ld" = yes; then + archive_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='${wl}' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec_CXX="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + whole_archive_flag_spec_CXX= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + ld_shlibs_CXX=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds_CXX='' + hardcode_direct_CXX=yes + hardcode_direct_absolute_CXX=yes + hardcode_libdir_separator_CXX=':' + link_all_deplibs_CXX=yes + file_list_spec_CXX='${wl}-f,' + + if test "$GXX" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct_CXX=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L_CXX=yes + hardcode_libdir_flag_spec_CXX='-L$libdir' + hardcode_libdir_separator_CXX= + fi + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + export_dynamic_flag_spec_CXX='${wl}-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + always_export_symbols_CXX=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag_CXX='-berok' + # Determine the default libpath from the value encoded in an empty + # executable. + if test "${lt_cv_aix_libpath+set}" = set; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath__CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX="/usr/lib:/lib" + fi + +fi + + aix_libpath=$lt_cv_aix_libpath__CXX +fi + + hardcode_libdir_flag_spec_CXX='${wl}-blibpath:$libdir:'"$aix_libpath" + + archive_expsym_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec_CXX='${wl}-R $libdir:/usr/lib:/lib' + allow_undefined_flag_CXX="-z nodefs" + archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + if test "${lt_cv_aix_libpath+set}" = set; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath__CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX="/usr/lib:/lib" + fi + +fi + + aix_libpath=$lt_cv_aix_libpath__CXX +fi + + hardcode_libdir_flag_spec_CXX='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag_CXX=' ${wl}-bernotok' + allow_undefined_flag_CXX=' ${wl}-berok' + if test "$with_gnu_ld" = yes; then + # We only use this code for GNU lds that support --whole-archive. + whole_archive_flag_spec_CXX='${wl}--whole-archive$convenience ${wl}--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec_CXX='$convenience' + fi + archive_cmds_need_lc_CXX=yes + # This is similar to how AIX traditionally builds its shared + # libraries. + archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag_CXX=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds_CXX='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + ld_shlibs_CXX=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec_CXX=' ' + allow_undefined_flag_CXX=unsupported + always_export_symbols_CXX=yes + file_list_spec_CXX='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=".dll" + # FIXME: Setting linknames here is a bad hack. + archive_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames=' + archive_expsym_cmds_CXX='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + $SED -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp; + else + $SED -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, CXX)='true' + enable_shared_with_static_runtimes_CXX=yes + # Don't use ranlib + old_postinstall_cmds_CXX='chmod 644 $oldlib' + postlink_cmds_CXX='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile="$lt_outputfile.exe" + lt_tool_outputfile="$lt_tool_outputfile.exe" + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, CXX) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec_CXX='-L$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-all-symbols' + allow_undefined_flag_CXX=unsupported + always_export_symbols_CXX=no + enable_shared_with_static_runtimes_CXX=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + archive_expsym_cmds_CXX='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs_CXX=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + + + archive_cmds_need_lc_CXX=no + hardcode_direct_CXX=no + hardcode_automatic_CXX=yes + hardcode_shlibpath_var_CXX=unsupported + if test "$lt_cv_ld_force_load" = "yes"; then + whole_archive_flag_spec_CXX='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + + else + whole_archive_flag_spec_CXX='' + fi + link_all_deplibs_CXX=yes + allow_undefined_flag_CXX="$_lt_dar_allow_undefined" + case $cc_basename in + ifort*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test "$_lt_dar_can_shared" = "yes"; then + output_verbose_link_cmd=func_echo_all + archive_cmds_CXX="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + module_cmds_CXX="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + archive_expsym_cmds_CXX="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + module_expsym_cmds_CXX="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + if test "$lt_cv_apple_cc_single_mod" != "yes"; then + archive_cmds_CXX="\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dsymutil}" + archive_expsym_cmds_CXX="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dar_export_syms}${_lt_dsymutil}" + fi + + else + ld_shlibs_CXX=no + fi + + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + ld_shlibs_CXX=no + ;; + + freebsd-elf*) + archive_cmds_need_lc_CXX=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + ld_shlibs_CXX=yes + ;; + + gnu*) + ;; + + haiku*) + archive_cmds_CXX='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + link_all_deplibs_CXX=yes + ;; + + hpux9*) + hardcode_libdir_flag_spec_CXX='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_CXX=: + export_dynamic_flag_spec_CXX='${wl}-E' + hardcode_direct_CXX=yes + hardcode_minus_L_CXX=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aCC*) + archive_cmds_CXX='$RM $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test "$GXX" = yes; then + archive_cmds_CXX='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test $with_gnu_ld = no; then + hardcode_libdir_flag_spec_CXX='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_CXX=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + export_dynamic_flag_spec_CXX='${wl}-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct_CXX=no + hardcode_shlibpath_var_CXX=no + ;; + *) + hardcode_direct_CXX=yes + hardcode_direct_absolute_CXX=yes + hardcode_minus_L_CXX=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test "$GXX" = yes; then + if test $with_gnu_ld = no; then + case $host_cpu in + hppa*64*) + archive_cmds_CXX='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + archive_cmds_CXX='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + interix[3-9]*) + hardcode_direct_CXX=no + hardcode_shlibpath_var_CXX=no + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + export_dynamic_flag_spec_CXX='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds_CXX='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds_CXX='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + archive_cmds_CXX='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test "$GXX" = yes; then + if test "$with_gnu_ld" = no; then + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` -o $lib' + fi + fi + link_all_deplibs_CXX=yes + ;; + esac + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_CXX=: + inherit_rpath_CXX=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + archive_expsym_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + archive_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + archive_cmds_need_lc_CXX=no + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + whole_archive_flag_spec_CXX='${wl}--whole-archive$convenience ${wl}--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [1-5].* | *pgcpp\ [1-5].*) + prelink_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + old_archive_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + archive_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib' + archive_expsym_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib' + ;; + esac + + hardcode_libdir_flag_spec_CXX='${wl}--rpath ${wl}$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + whole_archive_flag_spec_CXX='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive' + ;; + cxx*) + # Compaq C++ + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib ${wl}-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec_CXX='-rpath $libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + archive_cmds_CXX='$CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + if test "x$supports_anon_versioning" = xyes; then + archive_expsym_cmds_CXX='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + no_undefined_flag_CXX=' -zdefs' + archive_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + archive_expsym_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file ${wl}$export_symbols' + hardcode_libdir_flag_spec_CXX='-R$libdir' + whole_archive_flag_spec_CXX='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive' + compiler_needs_object_CXX=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds_CXX='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + hardcode_libdir_flag_spec_CXX='-R$libdir' + hardcode_direct_CXX=yes + hardcode_shlibpath_var_CXX=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + ld_shlibs_CXX=yes + ;; + + openbsd2*) + # C++ shared libraries are fairly broken + ld_shlibs_CXX=no + ;; + + openbsd*) + if test -f /usr/libexec/ld.so; then + hardcode_direct_CXX=yes + hardcode_shlibpath_var_CXX=no + hardcode_direct_absolute_CXX=yes + archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file,$export_symbols -o $lib' + export_dynamic_flag_spec_CXX='${wl}-E' + whole_archive_flag_spec_CXX="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + ld_shlibs_CXX=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + hardcode_libdir_separator_CXX=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) old_archive_cmds_CXX='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + cxx*) + case $host in + osf3*) + allow_undefined_flag_CXX=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds_CXX='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $soname `test -n "$verstring" && func_echo_all "${wl}-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib' + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + ;; + *) + allow_undefined_flag_CXX=' -expect_unresolved \*' + archive_cmds_CXX='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib' + archive_expsym_cmds_CXX='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname ${wl}-input ${wl}$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~ + $RM $lib.exp' + hardcode_libdir_flag_spec_CXX='-rpath $libdir' + ;; + esac + + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + allow_undefined_flag_CXX=' ${wl}-expect_unresolved ${wl}\*' + case $host in + osf3*) + archive_cmds_CXX='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + ;; + *) + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + ;; + esac + + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + archive_cmds_need_lc_CXX=yes + no_undefined_flag_CXX=' -zdefs' + archive_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G${allow_undefined_flag} ${wl}-M ${wl}$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + hardcode_libdir_flag_spec_CXX='-R$libdir' + hardcode_shlibpath_var_CXX=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + whole_archive_flag_spec_CXX='-z allextract$convenience -z defaultextract' + ;; + esac + link_all_deplibs_CXX=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + old_archive_cmds_CXX='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + no_undefined_flag_CXX=' ${wl}-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + else + # g++ 2.7 appears to require `-G' NOT `-shared' on this + # platform. + archive_cmds_CXX='$CC -G -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + fi + + hardcode_libdir_flag_spec_CXX='${wl}-R $wl$libdir' + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + whole_archive_flag_spec_CXX='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag_CXX='${wl}-z,text' + archive_cmds_need_lc_CXX=no + hardcode_shlibpath_var_CXX=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + archive_cmds_CXX='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag_CXX='${wl}-z,text' + allow_undefined_flag_CXX='${wl}-z,nodefs' + archive_cmds_need_lc_CXX=no + hardcode_shlibpath_var_CXX=no + hardcode_libdir_flag_spec_CXX='${wl}-R,$libdir' + hardcode_libdir_separator_CXX=':' + link_all_deplibs_CXX=yes + export_dynamic_flag_spec_CXX='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + archive_cmds_CXX='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + old_archive_cmds_CXX='$CC -Tprelink_objects $oldobjs~ + '"$old_archive_cmds_CXX" + reload_cmds_CXX='$CC -Tprelink_objects $reload_objs~ + '"$reload_cmds_CXX" + ;; + *) + archive_cmds_CXX='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 +$as_echo "$ld_shlibs_CXX" >&6; } + test "$ld_shlibs_CXX" = no && can_build_shared=no + + GCC_CXX="$GXX" + LD_CXX="$LD" + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + # Dependencies to place before and after the object being linked: +predep_objects_CXX= +postdep_objects_CXX= +predeps_CXX= +postdeps_CXX= +compiler_lib_search_path_CXX= + +cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF + + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case ${prev}${p} in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test $p = "-L" || + test $p = "-R"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test "$pre_test_object_deps_done" = no; then + case ${prev} in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$compiler_lib_search_path_CXX"; then + compiler_lib_search_path_CXX="${prev}${p}" + else + compiler_lib_search_path_CXX="${compiler_lib_search_path_CXX} ${prev}${p}" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$postdeps_CXX"; then + postdeps_CXX="${prev}${p}" + else + postdeps_CXX="${postdeps_CXX} ${prev}${p}" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test "$pre_test_object_deps_done" = no; then + if test -z "$predep_objects_CXX"; then + predep_objects_CXX="$p" + else + predep_objects_CXX="$predep_objects_CXX $p" + fi + else + if test -z "$postdep_objects_CXX"; then + postdep_objects_CXX="$p" + else + postdep_objects_CXX="$postdep_objects_CXX $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling CXX test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +case $host_os in +interix[3-9]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + predep_objects_CXX= + postdep_objects_CXX= + postdeps_CXX= + ;; + +linux*) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + + # The more standards-conforming stlport4 library is + # incompatible with the Cstd library. Avoid specifying + # it if it's in CXXFLAGS. Ignore libCrun as + # -library=stlport4 depends on it. + case " $CXX $CXXFLAGS " in + *" -library=stlport4 "*) + solaris_use_stlport4=yes + ;; + esac + + if test "$solaris_use_stlport4" != yes; then + postdeps_CXX='-library=Cstd -library=Crun' + fi + ;; + esac + ;; + +solaris*) + case $cc_basename in + CC* | sunCC*) + # The more standards-conforming stlport4 library is + # incompatible with the Cstd library. Avoid specifying + # it if it's in CXXFLAGS. Ignore libCrun as + # -library=stlport4 depends on it. + case " $CXX $CXXFLAGS " in + *" -library=stlport4 "*) + solaris_use_stlport4=yes + ;; + esac + + # Adding this requires a known-good setup of shared libraries for + # Sun compiler versions before 5.6, else PIC objects from an old + # archive will be linked into the output, leading to subtle bugs. + if test "$solaris_use_stlport4" != yes; then + postdeps_CXX='-library=Cstd -library=Crun' + fi + ;; + esac + ;; +esac + + +case " $postdeps_CXX " in +*" -lc "*) archive_cmds_need_lc_CXX=no ;; +esac + compiler_lib_search_dirs_CXX= +if test -n "${compiler_lib_search_path_CXX}"; then + compiler_lib_search_dirs_CXX=`echo " ${compiler_lib_search_path_CXX}" | ${SED} -e 's! -L! !g' -e 's!^ !!'` +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lt_prog_compiler_wl_CXX= +lt_prog_compiler_pic_CXX= +lt_prog_compiler_static_CXX= + + + # C++ specific cases for pic, static, wl, etc. + if test "$GXX" = yes; then + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_CXX='-Bstatic' + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + lt_prog_compiler_pic_CXX='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + lt_prog_compiler_pic_CXX='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic_CXX='-DDLL_EXPORT' + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic_CXX='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + lt_prog_compiler_pic_CXX= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + lt_prog_compiler_static_CXX= + ;; + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic_CXX=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + lt_prog_compiler_pic_CXX='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic_CXX='-fPIC -shared' + ;; + *) + lt_prog_compiler_pic_CXX='-fPIC' + ;; + esac + else + case $host_os in + aix[4-9]*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_CXX='-Bstatic' + else + lt_prog_compiler_static_CXX='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, CXX)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic_CXX='-DDLL_EXPORT' + ;; + dgux*) + case $cc_basename in + ec++*) + lt_prog_compiler_pic_CXX='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + lt_prog_compiler_pic_CXX='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='${wl}-a ${wl}archive' + if test "$host_cpu" != ia64; then + lt_prog_compiler_pic_CXX='+Z' + fi + ;; + aCC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='${wl}-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic_CXX='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu) + case $cc_basename in + KCC*) + # KAI C++ Compiler + lt_prog_compiler_wl_CXX='--backend -Wl,' + lt_prog_compiler_pic_CXX='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64 which still supported -KPIC. + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-fPIC' + lt_prog_compiler_static_CXX='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-fpic' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + lt_prog_compiler_pic_CXX= + lt_prog_compiler_static_CXX='-non_shared' + ;; + xlc* | xlC* | bgxl[cC]* | mpixl[cC]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-qpic' + lt_prog_compiler_static_CXX='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + lt_prog_compiler_wl_CXX='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + lt_prog_compiler_pic_CXX='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic_CXX='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + lt_prog_compiler_wl_CXX='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + lt_prog_compiler_pic_CXX='-pic' + ;; + cxx*) + # Digital/Compaq C++ + lt_prog_compiler_wl_CXX='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + lt_prog_compiler_pic_CXX= + lt_prog_compiler_static_CXX='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + lt_prog_compiler_wl_CXX='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + lt_prog_compiler_pic_CXX='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + lt_prog_compiler_pic_CXX='-pic' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + lcc*) + # Lucid + lt_prog_compiler_pic_CXX='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + lt_prog_compiler_pic_CXX='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + lt_prog_compiler_can_build_shared_CXX=no + ;; + esac + fi + +case $host_os in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic_CXX= + ;; + *) + lt_prog_compiler_pic_CXX="$lt_prog_compiler_pic_CXX -DPIC" + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } +if ${lt_cv_prog_compiler_pic_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_CXX=$lt_prog_compiler_pic_CXX +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_pic_CXX" >&6; } +lt_prog_compiler_pic_CXX=$lt_cv_prog_compiler_pic_CXX + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic_CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works... " >&6; } +if ${lt_cv_prog_compiler_pic_works_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works_CXX=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic_CXX -DPIC" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works_CXX=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works_CXX" >&6; } + +if test x"$lt_cv_prog_compiler_pic_works_CXX" = xyes; then + case $lt_prog_compiler_pic_CXX in + "" | " "*) ;; + *) lt_prog_compiler_pic_CXX=" $lt_prog_compiler_pic_CXX" ;; + esac +else + lt_prog_compiler_pic_CXX= + lt_prog_compiler_can_build_shared_CXX=no +fi + +fi + + + + + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl_CXX eval lt_tmp_static_flag=\"$lt_prog_compiler_static_CXX\" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if ${lt_cv_prog_compiler_static_works_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works_CXX=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works_CXX=yes + fi + else + lt_cv_prog_compiler_static_works_CXX=yes + fi + fi + $RM -r conftest* + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_static_works_CXX" >&6; } + +if test x"$lt_cv_prog_compiler_static_works_CXX" = xyes; then + : +else + lt_prog_compiler_static_CXX= +fi + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_CXX=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_CXX=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_CXX=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_CXX=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } + + + + +hard_links="nottested" +if test "$lt_cv_prog_compiler_c_o_CXX" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test "$hard_links" = no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms_CXX='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + case $host_os in + aix[4-9]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + # Also, AIX nm treats weak defined symbols like other global defined + # symbols, whereas GNU nm marks them as "W". + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + export_symbols_cmds_CXX='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds_CXX='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + export_symbols_cmds_CXX="$ltdll_cmds" + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + exclude_expsyms_CXX='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms_CXX='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' + ;; + esac + ;; + *) + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 +$as_echo "$ld_shlibs_CXX" >&6; } +test "$ld_shlibs_CXX" = no && can_build_shared=no + +with_gnu_ld_CXX=$with_gnu_ld + + + + + + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc_CXX" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc_CXX=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $archive_cmds_CXX in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } +if ${lt_cv_archive_cmds_need_lc_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl_CXX + pic_flag=$lt_prog_compiler_pic_CXX + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag_CXX + allow_undefined_flag_CXX= + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 + (eval $archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + then + lt_cv_archive_cmds_need_lc_CXX=no + else + lt_cv_archive_cmds_need_lc_CXX=yes + fi + allow_undefined_flag_CXX=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc_CXX" >&5 +$as_echo "$lt_cv_archive_cmds_need_lc_CXX" >&6; } + archive_cmds_need_lc_CXX=$lt_cv_archive_cmds_need_lc_CXX + ;; + esac + fi + ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } + +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix[4-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + library_names_spec='${libname}.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec="$LIB" + if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[23].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=yes + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[3-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + if ${lt_cv_shlibpath_overrides_runpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$lt_prog_compiler_wl_CXX\"; \ + LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec_CXX\"" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : + lt_cv_shlibpath_overrides_runpath=yes +fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + +fi + + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Append ld.so.conf contents to the search path + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd*) + version_type=sunos + sys_lib_dlsearch_path_spec="/usr/lib" + need_lib_prefix=no + # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs. + case $host_os in + openbsd3.3 | openbsd3.3.*) need_version=yes ;; + *) need_version=no ;; + esac + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[89] | openbsd2.[89].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext_cmds=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=freebsd-elf + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test "$with_gnu_ld" = yes; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test "$dynamic_linker" = no && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then + sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec" +fi +if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then + sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec" +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action_CXX= +if test -n "$hardcode_libdir_flag_spec_CXX" || + test -n "$runpath_var_CXX" || + test "X$hardcode_automatic_CXX" = "Xyes" ; then + + # We can hardcode non-existent directories. + if test "$hardcode_direct_CXX" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_TAGVAR(hardcode_shlibpath_var, CXX)" != no && + test "$hardcode_minus_L_CXX" != no; then + # Linking always hardcodes the temporary library directory. + hardcode_action_CXX=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action_CXX=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action_CXX=unsupported +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action_CXX" >&5 +$as_echo "$hardcode_action_CXX" >&6; } + +if test "$hardcode_action_CXX" = relink || + test "$inherit_rpath_CXX" = yes; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + + + + + + + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test "$_lt_caught_CXX_error" != yes + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + + + + + + + + + + + + ac_config_commands="$ac_config_commands libtool" + + + + +# Only expand once: + + +# Check whether --enable-experimental-libtool was given. +if test "${enable_experimental_libtool+set}" = set; then : + enableval=$enable_experimental_libtool; experimental_libtool=$enableval +else + experimental_libtool=no +fi + + +if test "$experimental_libtool" = "yes"; then + echo "using APR's libtool" + sh_libtool="`$apr_config --apr-libtool`" + LIBTOOL="$sh_libtool" + SVN_LIBTOOL="$sh_libtool" +else + sh_libtool="$abs_builddir/libtool" + SVN_LIBTOOL="\$(SHELL) $sh_libtool" +fi + + +lt_pversion=`$LIBTOOL --version 2>/dev/null|$SED -e 's/([^)]*)//g;s/^[^0-9]*//;s/[- ].*//g;q'` +lt_version=`echo $lt_pversion|$SED -e 's/\([a-z]*\)$/.\1/'` +lt_major_version=`echo $lt_version | cut -d'.' -f 1` + +svn_enable_static=yes +svn_enable_shared=yes + +# Check whether --enable-static was given. +if test "${enable_static+set}" = set; then : + enableval=$enable_static; svn_enable_static="$enableval" +else + svn_enable_static="yes" +fi + + +# Check whether --enable-shared was given. +if test "${enable_shared+set}" = set; then : + enableval=$enable_shared; svn_enable_shared="$enableval" +else + svn_enable_shared="yes" +fi + + +if test "$svn_enable_static" = "yes" && test "$svn_enable_shared" = "yes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: building both shared and static libraries" >&5 +$as_echo "$as_me: building both shared and static libraries" >&6;} +elif test "$svn_enable_static" = "yes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: building static libraries only" >&5 +$as_echo "$as_me: building static libraries only" >&6;} + LT_CFLAGS="-static $LT_CFLAGS" + LT_LDFLAGS="-static $LT_LDFLAGS" +elif test "$svn_enable_shared" = "yes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: building shared libraries only" >&5 +$as_echo "$as_me: building shared libraries only" >&6;} + if test "$lt_major_version" = "1" ; then + LT_CFLAGS="-prefer-pic $LT_CFLAGS" + elif test "$lt_major_version" = "2" ; then + LT_CFLAGS="-shared $LT_CFLAGS" + fi + LT_LDFLAGS="-shared $LT_LDFLAGS" +else + as_fn_error $? "cannot disable both shared and static libraries" "$LINENO" 5 +fi + +# Check whether --enable-all-static was given. +if test "${enable_all_static+set}" = set; then : + enableval=$enable_all_static; + if test "$enableval" = "yes" ; then + LT_LDFLAGS="-all-static $LT_LDFLAGS" + elif test "$enableval" != "no" ; then + as_fn_error $? "--enable-all-static doesn't accept argument" "$LINENO" 5 + fi + +fi + + + + + +# Check whether --enable-local-library-preloading was given. +if test "${enable_local_library_preloading+set}" = set; then : + enableval=$enable_local_library_preloading; + if test "$enableval" != "no"; then + if test "$svn_enable_shared" = "yes"; then + TRANSFORM_LIBTOOL_SCRIPTS="transform-libtool-scripts" + else + as_fn_error $? "--enable-local-library-preloading conflicts with --disable-shared" "$LINENO" 5 + fi + else + TRANSFORM_LIBTOOL_SCRIPTS="" + fi + +else + + TRANSFORM_LIBTOOL_SCRIPTS="" + +fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether libtool needs -no-undefined" >&5 +$as_echo_n "checking whether libtool needs -no-undefined... " >&6; } +case $host in + *-*-cygwin*) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + LT_NO_UNDEFINED="-no-undefined" + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + LT_NO_UNDEFINED="" + ;; +esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to avoid circular linkage at all costs" >&5 +$as_echo_n "checking whether to avoid circular linkage at all costs... " >&6; } +case $host in + *-*-cygwin*) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define SVN_AVOID_CIRCULAR_LINKAGE_AT_ALL_COSTS_HACK 1" >>confdefs.h + + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; +esac + +trang=yes + +# Check whether --with-trang was given. +if test "${with_trang+set}" = set; then : + withval=$with_trang; + trang="$withval" + +fi + +if test "$trang" = "yes"; then + # Extract the first word of "trang", so it can be a program name with args. +set dummy trang; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_TRANG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $TRANG in + [\\/]* | ?:[\\/]*) + ac_cv_path_TRANG="$TRANG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_TRANG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_TRANG" && ac_cv_path_TRANG="none" + ;; +esac +fi +TRANG=$ac_cv_path_TRANG +if test -n "$TRANG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TRANG" >&5 +$as_echo "$TRANG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +else + TRANG="$trang" + +fi + +doxygen=yes + +# Check whether --with-doxygen was given. +if test "${with_doxygen+set}" = set; then : + withval=$with_doxygen; + doxygen="$withval" + +fi + +if test "$doxygen" = "yes"; then + # Extract the first word of "doxygen", so it can be a program name with args. +set dummy doxygen; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_DOXYGEN+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $DOXYGEN in + [\\/]* | ?:[\\/]*) + ac_cv_path_DOXYGEN="$DOXYGEN" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_DOXYGEN="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_DOXYGEN" && ac_cv_path_DOXYGEN="none" + ;; +esac +fi +DOXYGEN=$ac_cv_path_DOXYGEN +if test -n "$DOXYGEN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DOXYGEN" >&5 +$as_echo "$DOXYGEN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +else + DOXYGEN="$doxygen" + +fi + + + + + +# Check whether --with-expat was given. +if test "${with_expat+set}" = set; then : + withval=$with_expat; svn_lib_expat="$withval" +else + svn_lib_expat="::expat" +fi + + +# APR-util accepts "builtin" as an argument to this option so if the user +# passed "builtin" pretend the user didn't specify the --with-expat option +# at all. Expat will (hopefully) be found in apr-util. +test "_$svn_lib_expat" = "_builtin" && svn_lib_expat="::expat" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Expat" >&5 +$as_echo_n "checking for Expat... " >&6; } +if test -n "`echo "$svn_lib_expat" | $EGREP ":.*:"`"; then + SVN_XML_INCLUDES="" + for i in `echo "$svn_lib_expat" | $SED -e "s/\([^:]*\):.*/\1/"`; do + SVN_XML_INCLUDES="$SVN_XML_INCLUDES -I$i" + done + SVN_XML_INCLUDES="${SVN_XML_INCLUDES## }" + for l in `echo "$svn_lib_expat" | $SED -e "s/.*:\([^:]*\):.*/\1/"`; do + LDFLAGS="$LDFLAGS -L$l" + done + for l in `echo "$svn_lib_expat" | $SED -e "s/.*:\([^:]*\)/\1/"`; do + SVN_XML_LIBS="$SVN_XML_LIBS -l$l" + done + SVN_XML_LIBS="${SVN_XML_LIBS## }" + old_CPPFLAGS="$CPPFLAGS" + old_LIBS="$LIBS" + CPPFLAGS="$CPPFLAGS $SVN_XML_INCLUDES" + LIBS="$LIBS $SVN_XML_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +int main() +{XML_ParserCreate(NULL);} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + svn_lib_expat="yes" +else + svn_lib_expat="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LIBS="$old_LIBS" + if test "$svn_lib_expat" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + SVN_XML_INCLUDES="" + SVN_XML_LIBS="" + CPPFLAGS="$CPPFLAGS $SVN_APRUTIL_INCLUDES" + if test "$enable_all_static" != "yes"; then + SVN_APRUTIL_LIBS="$SVN_APRUTIL_LIBS `$apu_config --libs`" + fi + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +int main() +{XML_ParserCreate(NULL);} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + svn_lib_expat="yes" +else + svn_lib_expat="no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + if test "$svn_lib_expat" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Expat found amongst libraries used by APR-Util, but Subversion libraries might be needlessly linked against additional unused libraries. It can be avoided by specifying exact location of Expat in argument of --with-expat option." >&5 +$as_echo "$as_me: WARNING: Expat found amongst libraries used by APR-Util, but Subversion libraries might be needlessly linked against additional unused libraries. It can be avoided by specifying exact location of Expat in argument of --with-expat option." >&2;} + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "Expat not found" "$LINENO" 5 + fi + fi + CPPFLAGS="$old_CPPFLAGS" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + if test "$svn_lib_expat" = "yes"; then + as_fn_error $? "--with-expat option requires argument" "$LINENO" 5 + elif test "$svn_lib_expat" = "no"; then + as_fn_error $? "Expat is required" "$LINENO" 5 + else + as_fn_error $? "Invalid syntax of argument of --with-expat option" "$LINENO" 5 + fi +fi + + + + +# Berkeley DB on SCO OpenServer needs -lsocket +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for socket in -lsocket" >&5 +$as_echo_n "checking for socket in -lsocket... " >&6; } +if ${ac_cv_lib_socket_socket+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsocket $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char socket (); +int +main () +{ +return socket (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_socket_socket=yes +else + ac_cv_lib_socket_socket=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_socket" >&5 +$as_echo "$ac_cv_lib_socket_socket" >&6; } +if test "x$ac_cv_lib_socket_socket" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBSOCKET 1 +_ACEOF + + LIBS="-lsocket $LIBS" + +fi + + +# Build the BDB filesystem library only if we have an appropriate +# version of Berkeley DB. +case "$host" in +powerpc-apple-darwin*) + # Berkeley DB 4.0 does not work on OS X. + SVN_FS_WANT_DB_MAJOR=4 + SVN_FS_WANT_DB_MINOR=1 + SVN_FS_WANT_DB_PATCH=25 + ;; +*) + SVN_FS_WANT_DB_MAJOR=4 + SVN_FS_WANT_DB_MINOR=0 + SVN_FS_WANT_DB_PATCH=14 + ;; +esac +# Look for libdb4.so first: + + db_version=$SVN_FS_WANT_DB_MAJOR.$SVN_FS_WANT_DB_MINOR.$SVN_FS_WANT_DB_PATCH + + +# Check whether --with-berkeley-db was given. +if test "${with_berkeley_db+set}" = set; then : + withval=$with_berkeley_db; + if test "$withval" = "no"; then + bdb_status=skip + elif test "$withval" = "yes"; then + apu_db_version="`$apu_config --db-version`" + if test $? -ne 0; then + as_fn_error $? "Can't determine whether apr-util is linked against a + proper version of Berkeley DB." "$LINENO" 5 + fi + + if test "$withval" = "yes"; then + if test "$apu_db_version" -lt "4"; then + as_fn_error $? "APR-UTIL was linked against Berkeley DB version $apu_db_version, + while version 4 or higher is required. Reinstall + APR-UTIL with the appropriate options." "$LINENO" 5 + fi + + bdb_status=required + + elif test "$apu_found" != "reconfig"; then + if test "$apu_db_version" -lt 4; then + as_fn_error $? "APR-UTIL was installed independently, it won't be + possible to use the specified Berkeley DB: $withval" "$LINENO" 5 + fi + + bdb_status=required + fi + else + if echo "$withval" | $EGREP ":.*:.*:" > /dev/null; then + svn_berkeley_db_header="`echo "$withval" | $SED -e "s/\([^:]*\):.*/\1/"`" + SVN_DB_INCLUDES="" + for i in `echo "$withval" | $SED -e "s/.*:\([^:]*\):[^:]*:.*/\1/"`; do + SVN_DB_INCLUDES="$SVN_DB_INCLUDES -I$i" + done + SVN_DB_INCLUDES="${SVN_DB_INCLUDES## }" + for l in `echo "$withval" | $SED -e "s/.*:[^:]*:\([^:]*\):.*/\1/"`; do + LDFLAGS="$LDFLAGS -L$l" + done + SVN_DB_LIBS="" + for l in `echo "$withval" | $SED -e "s/.*:\([^:]*\)/\1/"`; do + SVN_DB_LIBS="$SVN_DB_LIBS -l$l" + done + SVN_DB_LIBS="${SVN_DB_LIBS## }" + + bdb_status=required + else + as_fn_error $? "Invalid syntax of argument of --with-berkeley-db option" "$LINENO" 5 + fi + fi + +else + + # No --with-berkeley-db option: + # + # Check if APR-UTIL is providing the correct Berkeley DB version + # for us. + # + apu_db_version="`$apu_config --db-version`" + if test $? -ne 0; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected older version of APR-UTIL, trying to determine + whether apr-util is linked against Berkeley DB + $db_version" >&5 +$as_echo "$as_me: WARNING: Detected older version of APR-UTIL, trying to determine + whether apr-util is linked against Berkeley DB + $db_version" >&2;} + bdb_status=try-link + elif test "$apu_db_version" -lt "4"; then + bdb_status=skip + else + bdb_status=try-link + fi + +fi + + + if test "$bdb_status" = "skip"; then + svn_lib_berkeley_db=no + else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for availability of Berkeley DB" >&5 +$as_echo_n "checking for availability of Berkeley DB... " >&6; } + + svn_lib_berkeley_db_try_save_cppflags="$CPPFLAGS" + svn_lib_berkeley_db_try_save_libs="$LIBS" + + svn_check_berkeley_db_major=$SVN_FS_WANT_DB_MAJOR + svn_check_berkeley_db_minor=$SVN_FS_WANT_DB_MINOR + svn_check_berkeley_db_patch=$SVN_FS_WANT_DB_PATCH + + if test -z "$SVN_DB_LIBS"; then + # We pass --dbm-libs here since Debian has modified apu-config not + # to return -ldb unless --dbm-libs is passed. This may also produce + # extra output beyond -ldb but since we're only filtering for -ldb + # it won't matter to us. However, --dbm-libs was added to apu-config + # in 1.3.8 so it's possible the version we have doesn't support it + # so fallback without it if we get an error. + svn_db_libs_prefiltered="`$apu_config --libs --dbm-libs`" + if test $? -ne 0; then + svn_db_libs_prefiltered="`$apu_config --libs`" + fi + + # Extract only the -ldb.* flag from the libs supplied by apu-config + # Otherwise we get bit by the fact that expat might not be built yet + # Or that it resides in a non-standard location which we would have + # to compensate with using something like -R`$apu_config --prefix`/lib. + # + SVN_DB_LIBS="`echo \"$svn_db_libs_prefiltered\" | $SED -e 's/.*\(-ldb[^[:space:]]*\).*/\1/' | $EGREP -- '-ldb[^[:space:]]*'`" + fi + + CPPFLAGS="$SVN_DB_INCLUDES $SVN_APRUTIL_INCLUDES $CPPFLAGS" + LIBS="`$apu_config --ldflags` $SVN_DB_LIBS $LIBS" + + if test -n "$svn_berkeley_db_header"; then + SVN_DB_HEADER="#include <$svn_berkeley_db_header>" + svn_db_header="#include <$svn_berkeley_db_header>" + else + SVN_DB_HEADER="#include " + svn_db_header="#define APU_WANT_DB +#include " + fi + + + + if test "$cross_compiling" = yes; then : + svn_have_berkeley_db=yes + +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +$svn_db_header + +int main () +{ + int major, minor, patch; + + db_version (&major, &minor, &patch); + + /* Sanity check: ensure that db.h constants actually match the db library */ + if (major != DB_VERSION_MAJOR + || minor != DB_VERSION_MINOR + || patch != DB_VERSION_PATCH) + exit (1); + + /* Run-time check: ensure the library claims to be the correct version. */ + + if (major < $svn_check_berkeley_db_major) + exit (1); + if (major > $svn_check_berkeley_db_major) + exit (0); + + if (minor < $svn_check_berkeley_db_minor) + exit (1); + if (minor > $svn_check_berkeley_db_minor) + exit (0); + + if (patch >= $svn_check_berkeley_db_patch) + exit (0); + else + exit (1); +} + +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + svn_have_berkeley_db=yes +else + svn_have_berkeley_db=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + + CPPFLAGS="$svn_lib_berkeley_db_try_save_cppflags" + LIBS="$svn_lib_berkeley_db_try_save_libs" + + + if test "$svn_have_berkeley_db" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + svn_lib_berkeley_db=yes + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + svn_lib_berkeley_db=no + if test "$bdb_status" = "required"; then + as_fn_error $? "Berkeley DB $db_version or newer wasn't found." "$LINENO" 5 + fi + fi + fi + + + +cat >>confdefs.h <<_ACEOF +#define SVN_FS_WANT_DB_MAJOR $SVN_FS_WANT_DB_MAJOR +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define SVN_FS_WANT_DB_MINOR $SVN_FS_WANT_DB_MINOR +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define SVN_FS_WANT_DB_PATCH $SVN_FS_WANT_DB_PATCH +_ACEOF + + + + + + + +# Check whether --with-sasl was given. +if test "${with_sasl+set}" = set; then : + withval=$with_sasl; + with_sasl="$withval" + required="yes" + +else + + with_sasl="yes" + required="no" + +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to look for SASL" >&5 +$as_echo_n "checking whether to look for SASL... " >&6; } + + if test "${with_sasl}" = "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + svn_lib_sasl=no + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + saved_LDFLAGS="$LDFLAGS" + saved_CPPFLAGS="$CPPFLAGS" + + if test "$with_sasl" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Looking in default locations" >&5 +$as_echo "$as_me: Looking in default locations" >&6;} + ac_fn_c_check_header_mongrel "$LINENO" "sasl/sasl.h" "ac_cv_header_sasl_sasl_h" "$ac_includes_default" +if test "x$ac_cv_header_sasl_sasl_h" = xyes; then : + ac_fn_c_check_header_mongrel "$LINENO" "sasl/saslutil.h" "ac_cv_header_sasl_saslutil_h" "$ac_includes_default" +if test "x$ac_cv_header_sasl_saslutil_h" = xyes; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for prop_get in -lsasl2" >&5 +$as_echo_n "checking for prop_get in -lsasl2... " >&6; } +if ${ac_cv_lib_sasl2_prop_get+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsasl2 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char prop_get (); +int +main () +{ +return prop_get (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_sasl2_prop_get=yes +else + ac_cv_lib_sasl2_prop_get=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sasl2_prop_get" >&5 +$as_echo "$ac_cv_lib_sasl2_prop_get" >&6; } +if test "x$ac_cv_lib_sasl2_prop_get" = xyes; then : + svn_lib_sasl=yes +else + svn_lib_sasl=no +fi + +else + svn_lib_sasl=no +fi + + +else + svn_lib_sasl=no +fi + + + if test "$svn_lib_sasl" = "no"; then + with_sasl="/usr/local" + fi + else + svn_lib_sasl=no + fi + + if test "$svn_lib_sasl" = "no"; then + SVN_SASL_INCLUDES="-I${with_sasl}/include" + CPPFLAGS="$CPPFLAGS $SVN_SASL_INCLUDES" + LDFLAGS="$LDFLAGS ` + input_flags="-L${with_sasl}/lib" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + ac_fn_c_check_header_mongrel "$LINENO" "sasl/sasl.h" "ac_cv_header_sasl_sasl_h" "$ac_includes_default" +if test "x$ac_cv_header_sasl_sasl_h" = xyes; then : + ac_fn_c_check_header_mongrel "$LINENO" "sasl/saslutil.h" "ac_cv_header_sasl_saslutil_h" "$ac_includes_default" +if test "x$ac_cv_header_sasl_saslutil_h" = xyes; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for prop_get in -lsasl2" >&5 +$as_echo_n "checking for prop_get in -lsasl2... " >&6; } +if ${ac_cv_lib_sasl2_prop_get+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsasl2 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char prop_get (); +int +main () +{ +return prop_get (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_sasl2_prop_get=yes +else + ac_cv_lib_sasl2_prop_get=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sasl2_prop_get" >&5 +$as_echo "$ac_cv_lib_sasl2_prop_get" >&6; } +if test "x$ac_cv_lib_sasl2_prop_get" = xyes; then : + svn_lib_sasl=yes +else + svn_lib_sasl=no +fi + +else + svn_lib_sasl=no +fi + + +else + svn_lib_sasl=no +fi + + + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for availability of Cyrus SASL v2" >&5 +$as_echo_n "checking for availability of Cyrus SASL v2... " >&6; } + if test "$svn_lib_sasl" = "yes"; then + SVN_SASL_LIBS="-lsasl2" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + + if test "$required" = "yes"; then + as_fn_error $? "Could not find Cyrus SASL v2" "$LINENO" 5 + fi + + SVN_SASL_INCLUDES="" + LDFLAGS="$saved_LDFLAGS" + fi + + CPPFLAGS="$saved_CPPFLAGS" + fi + + + + + +if test "$svn_lib_sasl" = "yes"; then + +$as_echo "#define SVN_HAVE_SASL 1" >>confdefs.h + +fi + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Mach-O dynamic module iteration functions" >&5 +$as_echo_n "checking for Mach-O dynamic module iteration functions... " >&6; } + if test "$cross_compiling" = yes; then : + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run test program while cross compiling +See \`config.log' for more details" "$LINENO" 5; } +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main () +{ + + const struct mach_header *header = _dyld_get_image_header(0); + const char *name = _dyld_get_image_name(0); + if (name && header) return 0; + return 1; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + + +$as_echo "#define SVN_HAVE_MACHO_ITERATE 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Mac OS property list utilities" >&5 +$as_echo_n "checking for Mac OS property list utilities... " >&6; } + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #if !defined(MAC_OS_X_VERSION_MAX_ALLOWED) \ + || !defined(MAC_OS_X_VERSION_10_0) \ + || (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_0) + #error ProperyList API unavailable. + #endif + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + + LIBS="$LIBS -framework CoreFoundation" + +$as_echo "#define SVN_HAVE_MACOS_PLIST 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + + + # Check whether --enable-keychain was given. +if test "${enable_keychain+set}" = set; then : + enableval=$enable_keychain; enable_keychain=$enableval +else + enable_keychain=yes +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Mac OS KeyChain Services" >&5 +$as_echo_n "checking for Mac OS KeyChain Services... " >&6; } + + if test "$enable_keychain" = "yes"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #if !defined(MAC_OS_X_VERSION_MAX_ALLOWED) \ + || !defined(MAC_OS_X_VERSION_10_2) \ + || (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_2) + #error KeyChain API unavailable. + #endif + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + LIBS="$LIBS -framework Security" + LIBS="$LIBS -framework CoreServices" + +$as_echo "#define SVN_HAVE_KEYCHAIN_SERVICES 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +else + + enable_keychain=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether APR has support for DSOs" >&5 +$as_echo_n "checking whether APR has support for DSOs... " >&6; } +old_CPPFLAGS="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#if !APR_HAS_DSO +#error +#endif +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + APR_HAS_DSO="yes" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + APR_HAS_DSO="no" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f conftest.err conftest.i conftest.$ac_ext +CPPFLAGS="$old_CPPFLAGS" + + + +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for D-Bus .pc file" >&5 +$as_echo_n "checking for D-Bus .pc file... " >&6; } + if $PKG_CONFIG --exists dbus-1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + old_CPPFLAGS="$CPPFLAGS" + old_LIBS="$LIBS" + DBUS_CPPFLAGS="`$PKG_CONFIG --cflags dbus-1`" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking D-Bus version" >&5 +$as_echo_n "checking D-Bus version... " >&6; } + DBUS_VERSION="`$PKG_CONFIG --modversion dbus-1`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DBUS_VERSION" >&5 +$as_echo "$DBUS_VERSION" >&6; } + # D-Bus 0.* requires DBUS_API_SUBJECT_TO_CHANGE + if test -n "`echo "$DBUS_VERSION" | $EGREP '^0\.[[:digit:]]+'`"; then + DBUS_CPPFLAGS="$DBUS_CPPFLAGS -DDBUS_API_SUBJECT_TO_CHANGE" + fi + DBUS_LIBS="`$PKG_CONFIG --libs dbus-1`" + CPPFLAGS="$CPPFLAGS $DBUS_CPPFLAGS" + LIBS="$LIBS $DBUS_LIBS" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for D-Bus" >&5 +$as_echo_n "checking for D-Bus... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +int main() +{dbus_bus_get(DBUS_BUS_SESSION, NULL);} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + HAVE_DBUS="yes" +else + HAVE_DBUS="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test "$HAVE_DBUS" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + CPPFLAGS="$old_CPPFLAGS" + LIBS="$old_LIBS" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi +fi + + + +# Check whether --with-gpg_agent was given. +if test "${with_gpg_agent+set}" = set; then : + withval=$with_gpg_agent; +else + with_gpg_agent=yes +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support GPG-Agent" >&5 +$as_echo_n "checking whether to support GPG-Agent... " >&6; } +if test "$with_gpg_agent" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define SVN_HAVE_GPG_AGENT 1" >>confdefs.h + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + + +# Check whether --with-gnome_keyring was given. +if test "${with_gnome_keyring+set}" = set; then : + withval=$with_gnome_keyring; with_gnome_keyring="$withval" +else + with_gnome_keyring=auto +fi + + +found_gnome_keyring=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to look for GNOME Keyring" >&5 +$as_echo_n "checking whether to look for GNOME Keyring... " >&6; } +if test "$with_gnome_keyring" != "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + if test "$svn_enable_shared" = "yes"; then + if test "$APR_HAS_DSO" = "yes"; then + if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GLib and GNOME Keyring .pc files" >&5 +$as_echo_n "checking for GLib and GNOME Keyring .pc files... " >&6; } + if $PKG_CONFIG --exists glib-2.0 gnome-keyring-1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + old_CPPFLAGS="$CPPFLAGS" + SVN_GNOME_KEYRING_INCLUDES="`$PKG_CONFIG --cflags glib-2.0 gnome-keyring-1`" + CPPFLAGS="$CPPFLAGS $SVN_GNOME_KEYRING_INCLUDES" + ac_fn_c_check_header_mongrel "$LINENO" "gnome-keyring.h" "ac_cv_header_gnome_keyring_h" "$ac_includes_default" +if test "x$ac_cv_header_gnome_keyring_h" = xyes; then : + found_gnome_keyring=yes +else + found_gnome_keyring=no +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNOME Keyring" >&5 +$as_echo_n "checking for GNOME Keyring... " >&6; } + if test "$found_gnome_keyring" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define SVN_HAVE_GNOME_KEYRING 1" >>confdefs.h + + CPPFLAGS="$old_CPPFLAGS" + SVN_GNOME_KEYRING_LIBS="`$PKG_CONFIG --libs glib-2.0 gnome-keyring-1`" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + if test "$with_gnome_keyring" = "yes"; then + as_fn_error $? "cannot find GNOME Keyring" "$LINENO" 5 + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + if test "$with_gnome_keyring" = "yes"; then + as_fn_error $? "cannot find GLib and GNOME Keyring .pc files." "$LINENO" 5 + else + with_gnome_keyring=no + fi + fi + else + if test "$with_gnome_keyring" = "yes"; then + as_fn_error $? "cannot find pkg-config. GNOME Keyring requires this." "$LINENO" 5 + else + with_gnome_keyring=no + fi + fi + else + if test "$with_gnome_keyring" = "yes"; then + as_fn_error $? "APR does not have support for DSOs. GNOME Keyring requires this." "$LINENO" 5 + else + with_gnome_keyring=no + fi + fi + else + if test "$with_gnome_keyring" = "yes"; then + as_fn_error $? "--with-gnome-keyring conflicts with --disable-shared" "$LINENO" 5 + else + with_gnome_keyring=no + fi + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + +# Check whether --enable-ev2-impl was given. +if test "${enable_ev2_impl+set}" = set; then : + enableval=$enable_ev2_impl; enable_ev2_impl=$enableval +else + enable_ev2_impl=no +fi + +if test "$enable_ev2_impl" = "yes"; then + +$as_echo "#define ENABLE_EV2_IMPL 1" >>confdefs.h + +fi + + + +# Check whether --enable-nls was given. +if test "${enable_nls+set}" = set; then : + enableval=$enable_nls; enable_nls=$enableval +else + enable_nls=yes +fi + + +USE_NLS="no" +if test "$enable_nls" = "yes"; then + # Extract the first word of "msgfmt", so it can be a program name with args. +set dummy msgfmt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_MSGFMT+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MSGFMT in + [\\/]* | ?:[\\/]*) + ac_cv_path_MSGFMT="$MSGFMT" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_MSGFMT="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_MSGFMT" && ac_cv_path_MSGFMT="none" + ;; +esac +fi +MSGFMT=$ac_cv_path_MSGFMT +if test -n "$MSGFMT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MSGFMT" >&5 +$as_echo "$MSGFMT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + # Extract the first word of "msgmerge", so it can be a program name with args. +set dummy msgmerge; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_MSGMERGE+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MSGMERGE in + [\\/]* | ?:[\\/]*) + ac_cv_path_MSGMERGE="$MSGMERGE" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_MSGMERGE="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_MSGMERGE" && ac_cv_path_MSGMERGE="none" + ;; +esac +fi +MSGMERGE=$ac_cv_path_MSGMERGE +if test -n "$MSGMERGE"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MSGMERGE" >&5 +$as_echo "$MSGMERGE" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + # Extract the first word of "xgettext", so it can be a program name with args. +set dummy xgettext; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_XGETTEXT+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $XGETTEXT in + [\\/]* | ?:[\\/]*) + ac_cv_path_XGETTEXT="$XGETTEXT" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_XGETTEXT="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_XGETTEXT" && ac_cv_path_XGETTEXT="none" + ;; +esac +fi +XGETTEXT=$ac_cv_path_XGETTEXT +if test -n "$XGETTEXT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $XGETTEXT" >&5 +$as_echo "$XGETTEXT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + if test "$MSGFMT" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing bindtextdomain" >&5 +$as_echo_n "checking for library containing bindtextdomain... " >&6; } +if ${ac_cv_search_bindtextdomain+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char bindtextdomain (); +int +main () +{ +return bindtextdomain (); + ; + return 0; +} +_ACEOF +for ac_lib in '' intl; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_bindtextdomain=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_bindtextdomain+:} false; then : + break +fi +done +if ${ac_cv_search_bindtextdomain+:} false; then : + +else + ac_cv_search_bindtextdomain=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_bindtextdomain" >&5 +$as_echo "$ac_cv_search_bindtextdomain" >&6; } +ac_res=$ac_cv_search_bindtextdomain +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +else + + enable_nls="no" + +fi + + if test "$enable_nls" = "no"; then + # Destroy the cached result so we can test again + unset ac_cv_search_bindtextdomain + # On some systems, libintl needs libiconv to link properly, + # so try again with -liconv. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing bindtextdomain" >&5 +$as_echo_n "checking for library containing bindtextdomain... " >&6; } +if ${ac_cv_search_bindtextdomain+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char bindtextdomain (); +int +main () +{ +return bindtextdomain (); + ; + return 0; +} +_ACEOF +for ac_lib in '' intl; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib -liconv $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_bindtextdomain=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_bindtextdomain+:} false; then : + break +fi +done +if ${ac_cv_search_bindtextdomain+:} false; then : + +else + ac_cv_search_bindtextdomain=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_bindtextdomain" >&5 +$as_echo "$ac_cv_search_bindtextdomain" >&6; } +ac_res=$ac_cv_search_bindtextdomain +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + + enable_nls="yes" + # This is here so that -liconv ends up in LIBS + # if it worked with -liconv. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libiconv_open in -liconv" >&5 +$as_echo_n "checking for libiconv_open in -liconv... " >&6; } +if ${ac_cv_lib_iconv_libiconv_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-liconv $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char libiconv_open (); +int +main () +{ +return libiconv_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_iconv_libiconv_open=yes +else + ac_cv_lib_iconv_libiconv_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_iconv_libiconv_open" >&5 +$as_echo "$ac_cv_lib_iconv_libiconv_open" >&6; } +if test "x$ac_cv_lib_iconv_libiconv_open" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBICONV 1 +_ACEOF + + LIBS="-liconv $LIBS" + +fi + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: bindtextdomain() not found. Disabling NLS." >&5 +$as_echo "$as_me: WARNING: bindtextdomain() not found. Disabling NLS." >&2;} + enable_nls="no" + +fi + + fi + if test "$enable_nls" = "yes"; then + +$as_echo "#define ENABLE_NLS 1" >>confdefs.h + + USE_NLS="yes" + fi + fi +fi + + + +GETTEXT_CODESET=\# +NO_GETTEXT_CODESET=\# +if test $USE_NLS = "yes"; then + for ac_func in bind_textdomain_codeset +do : + ac_fn_c_check_func "$LINENO" "bind_textdomain_codeset" "ac_cv_func_bind_textdomain_codeset" +if test "x$ac_cv_func_bind_textdomain_codeset" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_BIND_TEXTDOMAIN_CODESET 1 +_ACEOF + GETTEXT_CODESET="" +else + NO_GETTEXT_CODESET="" +fi +done + +fi + + + +# Check if we are using GNU gettext. +GNU_GETTEXT=no +MSGFMTFLAGS='' +if test $USE_NLS = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we are using GNU gettext" >&5 +$as_echo_n "checking if we are using GNU gettext... " >&6; } + if $MSGFMT --version 2>&1 | $EGREP GNU > /dev/null; then + GNU_GETTEXT=yes + MSGFMTFLAGS='-c' + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GNU_GETTEXT" >&5 +$as_echo "$GNU_GETTEXT" >&6; } +fi + + + +libmagic_found=no + + +# Check whether --with-libmagic was given. +if test "${with_libmagic+set}" = set; then : + withval=$with_libmagic; + if test "$withval" = "yes" ; then + ac_fn_c_check_header_mongrel "$LINENO" "magic.h" "ac_cv_header_magic_h" "$ac_includes_default" +if test "x$ac_cv_header_magic_h" = xyes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for magic_open in -lmagic" >&5 +$as_echo_n "checking for magic_open in -lmagic... " >&6; } +if ${ac_cv_lib_magic_magic_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lmagic $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char magic_open (); +int +main () +{ +return magic_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_magic_magic_open=yes +else + ac_cv_lib_magic_magic_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_magic_magic_open" >&5 +$as_echo "$ac_cv_lib_magic_magic_open" >&6; } +if test "x$ac_cv_lib_magic_magic_open" = xyes; then : + libmagic_found="builtin" +fi + + +fi + + + libmagic_prefix="the default locations" + elif test "$withval" != "no"; then + libmagic_prefix=$withval + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS -I$libmagic_prefix/include" + for ac_header in magic.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "magic.h" "ac_cv_header_magic_h" "$ac_includes_default" +if test "x$ac_cv_header_magic_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_MAGIC_H 1 +_ACEOF + + save_ldflags="$LDFLAGS" + LDFLAGS="-L$libmagic_prefix/lib" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for magic_open in -lmagic" >&5 +$as_echo_n "checking for magic_open in -lmagic... " >&6; } +if ${ac_cv_lib_magic_magic_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lmagic $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char magic_open (); +int +main () +{ +return magic_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_magic_magic_open=yes +else + ac_cv_lib_magic_magic_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_magic_magic_open" >&5 +$as_echo "$ac_cv_lib_magic_magic_open" >&6; } +if test "x$ac_cv_lib_magic_magic_open" = xyes; then : + libmagic_found="yes" +fi + + LDFLAGS="$save_ldflags" + +fi + +done + + CPPFLAGS="$save_cppflags" + fi + if test "$withval" != "no" && test "$libmagic_found" = "no"; then + as_fn_error $? "--with-libmagic requested, but libmagic not found at $libmagic_prefix" "$LINENO" 5 + fi + +else + + ac_fn_c_check_header_mongrel "$LINENO" "magic.h" "ac_cv_header_magic_h" "$ac_includes_default" +if test "x$ac_cv_header_magic_h" = xyes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for magic_open in -lmagic" >&5 +$as_echo_n "checking for magic_open in -lmagic... " >&6; } +if ${ac_cv_lib_magic_magic_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lmagic $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char magic_open (); +int +main () +{ +return magic_open (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_magic_magic_open=yes +else + ac_cv_lib_magic_magic_open=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_magic_magic_open" >&5 +$as_echo "$ac_cv_lib_magic_magic_open" >&6; } +if test "x$ac_cv_lib_magic_magic_open" = xyes; then : + libmagic_found="builtin" +fi + + +fi + + + +fi + + +if test "$libmagic_found" != "no"; then + +$as_echo "#define SVN_HAVE_LIBMAGIC 1" >>confdefs.h + + SVN_MAGIC_LIBS="-lmagic" +fi + +if test "$libmagic_found" = "yes"; then + SVN_MAGIC_INCLUDES="-I$libmagic_prefix/include" + LDFLAGS="$LDFLAGS ` + input_flags="-L$libmagic_prefix/lib" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" +fi + + + + + + +# Check whether --with-kwallet was given. +if test "${with_kwallet+set}" = set; then : + withval=$with_kwallet; svn_lib_kwallet="$withval" +else + svn_lib_kwallet=no +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to look for KWallet" >&5 +$as_echo_n "checking whether to look for KWallet... " >&6; } + if test "$svn_lib_kwallet" != "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + if test "$svn_enable_shared" = "yes"; then + if test "$APR_HAS_DSO" = "yes"; then + if test -n "$PKG_CONFIG"; then + if test "$HAVE_DBUS" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for QtCore, QtDBus, QtGui" >&5 +$as_echo_n "checking for QtCore, QtDBus, QtGui... " >&6; } + if $PKG_CONFIG --exists QtCore QtDBus QtGui; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + if test "$svn_lib_kwallet" != "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for kde4-config" >&5 +$as_echo_n "checking for kde4-config... " >&6; } + KDE4_CONFIG="$svn_lib_kwallet/bin/kde4-config" + if test -f "$KDE4_CONFIG" && test -x "$KDE4_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + KDE4_CONFIG="" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + else + # Extract the first word of "kde4-config", so it can be a program name with args. +set dummy kde4-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_KDE4_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $KDE4_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_KDE4_CONFIG="$KDE4_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_KDE4_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +KDE4_CONFIG=$ac_cv_path_KDE4_CONFIG +if test -n "$KDE4_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $KDE4_CONFIG" >&5 +$as_echo "$KDE4_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi + if test -n "$KDE4_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for KWallet" >&5 +$as_echo_n "checking for KWallet... " >&6; } + old_CXXFLAGS="$CXXFLAGS" + old_LDFLAGS="$LDFLAGS" + old_LIBS="$LIBS" + for d in `$PKG_CONFIG --cflags QtCore QtDBus QtGui`; do + if test -n "`echo "$d" | $EGREP -- '^-D[^[:space:]]*'`"; then + CPPFLAGS="$CPPFLAGS $d" + fi + done + qt_include_dirs="`$PKG_CONFIG --cflags-only-I QtCore QtDBus QtGui`" + kde_dir="`$KDE4_CONFIG --prefix`" + SVN_KWALLET_INCLUDES="$DBUS_CPPFLAGS $qt_include_dirs -I$kde_dir/include" + qt_libs_other_options="`$PKG_CONFIG --libs-only-other QtCore QtDBus QtGui`" + SVN_KWALLET_LIBS="$DBUS_LIBS -lQtCore -lQtDBus -lQtGui -lkdecore -lkdeui $qt_libs_other_options" + CXXFLAGS="$CXXFLAGS $SVN_KWALLET_INCLUDES" + LIBS="$LIBS $SVN_KWALLET_LIBS" + qt_lib_dirs="`$PKG_CONFIG --libs-only-L QtCore QtDBus QtGui`" + kde_lib_suffix="`$KDE4_CONFIG --libsuffix`" + LDFLAGS="$old_LDFLAGS ` + input_flags="$qt_lib_dirs -L$kde_dir/lib$kde_lib_suffix" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +int main() +{KWallet::Wallet::walletList();} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + svn_lib_kwallet="yes" +else + svn_lib_kwallet="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + if test "$svn_lib_kwallet" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + CXXFLAGS="$old_CXXFLAGS" + LIBS="$old_LIBS" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "cannot find KWallet" "$LINENO" 5 + fi + else + as_fn_error $? "cannot find kde4-config" "$LINENO" 5 + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "cannot find QtCore, QtDBus, QtGui" "$LINENO" 5 + fi + else + as_fn_error $? "cannot find D-Bus" "$LINENO" 5 + fi + else + as_fn_error $? "cannot find pkg-config" "$LINENO" 5 + fi + else + as_fn_error $? "APR does not have support for DSOs" "$LINENO" 5 + fi + else + as_fn_error $? "--with-kwallet conflicts with --disable-shared" "$LINENO" 5 + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + + + + +if test "$svn_lib_kwallet" = "yes"; then + +$as_echo "#define SVN_HAVE_KWALLET 1" >>confdefs.h + +fi + +# Check whether --enable-plaintext-password-storage was given. +if test "${enable_plaintext_password_storage+set}" = set; then : + enableval=$enable_plaintext_password_storage; + if test "$enableval" = "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling plaintext password/passphrase storage" >&5 +$as_echo "$as_me: Disabling plaintext password/passphrase storage" >&6;} + +$as_echo "#define SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE 1" >>confdefs.h + + fi + +fi + + + +INSTALL_STATIC_RULES="install-bin install-docs" +INSTALL_RULES="install-fsmod-lib install-ramod-lib install-lib install-include install-static" +INSTALL_RULES="$INSTALL_RULES $INSTALL_APACHE_RULE" +BUILD_RULES="fsmod-lib ramod-lib lib bin test sub-test $BUILD_APACHE_RULE tools" + +if test "$svn_lib_berkeley_db" = "yes"; then + BUILD_RULES="$BUILD_RULES bdb-lib bdb-test" + INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-fsmod-lib/install-fsmod-lib install-bdb-lib/'`" + INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-bdb-lib" + BDB_TEST_DEPS="\$(BDB_TEST_DEPS)" + BDB_TEST_PROGRAMS="\$(BDB_TEST_PROGRAMS)" +fi + +if test "$svn_lib_serf" = "yes"; then + BUILD_RULES="$BUILD_RULES serf-lib" + INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-ramod-lib/install-ramod-lib install-serf-lib/'`" + INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-serf-lib" +fi + +if test "$svn_lib_kwallet" = "yes"; then + BUILD_RULES="$BUILD_RULES kwallet-lib" + INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-kwallet-lib/'`" + INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-kwallet-lib" +fi + +if test "$found_gnome_keyring" = "yes"; then + BUILD_RULES="$BUILD_RULES gnome-keyring-lib" + INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-gnome-keyring-lib/'`" + INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-gnome-keyring-lib" +fi + +if test "$USE_NLS" = "yes"; then + BUILD_RULES="$BUILD_RULES locale" + INSTALL_RULES="$INSTALL_RULES install-locale" +fi + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for an ANSI C-conforming const" >&5 +$as_echo_n "checking for an ANSI C-conforming const... " >&6; } +if ${ac_cv_c_const+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + +#ifndef __cplusplus + /* Ultrix mips cc rejects this sort of thing. */ + typedef int charset[2]; + const charset cs = { 0, 0 }; + /* SunOS 4.1.1 cc rejects this. */ + char const *const *pcpcc; + char **ppc; + /* NEC SVR4.0.2 mips cc rejects this. */ + struct point {int x, y;}; + static struct point const zero = {0,0}; + /* AIX XL C 1.02.0.0 rejects this. + It does not let you subtract one const X* pointer from another in + an arm of an if-expression whose if-part is not a constant + expression */ + const char *g = "string"; + pcpcc = &g + (g ? g-g : 0); + /* HPUX 7.0 cc rejects these. */ + ++pcpcc; + ppc = (char**) pcpcc; + pcpcc = (char const *const *) ppc; + { /* SCO 3.2v4 cc rejects this sort of thing. */ + char tx; + char *t = &tx; + char const *s = 0 ? (char *) 0 : (char const *) 0; + + *t++ = 0; + if (s) return 0; + } + { /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ + int x[] = {25, 17}; + const int *foo = &x[0]; + ++foo; + } + { /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ + typedef const int *iptr; + iptr p = 0; + ++p; + } + { /* AIX XL C 1.02.0.0 rejects this sort of thing, saying + "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ + struct s { int j; const int *ap[3]; } bx; + struct s *b = &bx; b->j = 5; + } + { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ + const int foo = 10; + if (!foo) return 0; + } + return !cs[0] && !zero.x; +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_const=yes +else + ac_cv_c_const=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_const" >&5 +$as_echo "$ac_cv_c_const" >&6; } +if test $ac_cv_c_const = no; then + +$as_echo "#define const /**/" >>confdefs.h + +fi + + +ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" +if test "x$ac_cv_type_size_t" = xyes; then : + +else + +cat >>confdefs.h <<_ACEOF +#define size_t unsigned int +_ACEOF + +fi + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for working memcmp" >&5 +$as_echo_n "checking for working memcmp... " >&6; } +if ${ac_cv_func_memcmp_working+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + ac_cv_func_memcmp_working=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ + + /* Some versions of memcmp are not 8-bit clean. */ + char c0 = '\100', c1 = '\200', c2 = '\201'; + if (memcmp(&c0, &c2, 1) >= 0 || memcmp(&c1, &c2, 1) >= 0) + return 1; + + /* The Next x86 OpenStep bug shows up only when comparing 16 bytes + or more and with at least one buffer not starting on a 4-byte boundary. + William Lewis provided this test program. */ + { + char foo[21]; + char bar[21]; + int i; + for (i = 0; i < 4; i++) + { + char *a = foo + i; + char *b = bar + i; + strcpy (a, "--------01111111"); + strcpy (b, "--------10000000"); + if (memcmp (a, b, 16) >= 0) + return 1; + } + return 0; + } + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + ac_cv_func_memcmp_working=yes +else + ac_cv_func_memcmp_working=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_memcmp_working" >&5 +$as_echo "$ac_cv_func_memcmp_working" >&6; } +test $ac_cv_func_memcmp_working = no && case " $LIBOBJS " in + *" memcmp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS memcmp.$ac_objext" + ;; +esac + + + +for ac_func in vprintf +do : + ac_fn_c_check_func "$LINENO" "vprintf" "ac_cv_func_vprintf" +if test "x$ac_cv_func_vprintf" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_VPRINTF 1 +_ACEOF + +ac_fn_c_check_func "$LINENO" "_doprnt" "ac_cv_func__doprnt" +if test "x$ac_cv_func__doprnt" = xyes; then : + +$as_echo "#define HAVE_DOPRNT 1" >>confdefs.h + +fi + +fi +done + + + +for ac_func in symlink readlink +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + +for ac_header in sys/utsname.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "sys/utsname.h" "ac_cv_header_sys_utsname_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_utsname_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_SYS_UTSNAME_H 1 +_ACEOF + for ac_func in uname +do : + ac_fn_c_check_func "$LINENO" "uname" "ac_cv_func_uname" +if test "x$ac_cv_func_uname" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_UNAME 1 +_ACEOF + +fi +done + +fi + +done + + +ac_fn_c_check_header_mongrel "$LINENO" "termios.h" "ac_cv_header_termios_h" "$ac_includes_default" +if test "x$ac_cv_header_termios_h" = xyes; then : + + for ac_func in tcgetattr tcsetattr +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + + +$as_echo "#define HAVE_TERMIOS_H 1" >>confdefs.h + + +fi +done + + +fi + + + + + +# Check whether --with-openssl was given. +if test "${with_openssl+set}" = set; then : + withval=$with_openssl; +fi + + +# Check whether --enable-debug was given. +if test "${enable_debug+set}" = set; then : + enableval=$enable_debug; + if test "$enableval" = "yes" ; then + enable_debugging="yes" + else + enable_debugging="no" + fi + +else + + # Neither --enable-debug nor --disable-debug was passed. + enable_debugging="maybe" + +fi + + +# Check whether --enable-optimize was given. +if test "${enable_optimize+set}" = set; then : + enableval=$enable_optimize; + if test "$enableval" = "yes" ; then + enable_optimization="yes" + else + enable_optimization="no" + fi + +else + + # Neither --enable-optimize nor --disable-optimize was passed. + enable_optimization="maybe" + +fi + + +# Check whether --enable-disallowing-of-undefined-references was given. +if test "${enable_disallowing_of_undefined_references+set}" = set; then : + enableval=$enable_disallowing_of_undefined_references; +fi + +if test "$enable_disallowing_of_undefined_references" != "yes" && test "`uname`" != "Linux"; then + enable_disallowing_of_undefined_references="no" +fi +if test "$enable_disallowing_of_undefined_references" != "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -Wl,--no-undefined" >&5 +$as_echo_n "checking for -Wl,--no-undefined... " >&6; } + old_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -Wl,--no-undefined" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(){;} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + svn_wl_no_undefined="yes" +else + svn_wl_no_undefined="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS="$old_LDFLAGS" + if test "$svn_wl_no_undefined" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + for library_dir in "$abs_srcdir/subversion/libsvn_"*; do + eval "`basename $library_dir`_LDFLAGS=-Wl,--no-undefined" + done + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + if test "$enable_disallowing_of_undefined_references" = "yes"; then + as_fn_error $? "--enable-disallowing-of-undefined-references explicitly requested, but -Wl,--no-undefined not supported" "$LINENO" 5 + fi + fi +fi + + + + + + + + + + + + + + + + + + +# Check whether --enable-maintainer-mode was given. +if test "${enable_maintainer_mode+set}" = set; then : + enableval=$enable_maintainer_mode; + if test "$enableval" = "yes" ; then + if test "$enable_debugging" = "no" ; then + as_fn_error $? "Can't have --disable-debug and --enable-maintainer-mode" "$LINENO" 5 + fi + enable_debugging=yes + + if test "$GCC" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: maintainer-mode: adding GCC warning flags" >&5 +$as_echo "$as_me: maintainer-mode: adding GCC warning flags" >&6;} + + + CFLAGS_KEEP="$CFLAGS" + CFLAGS="" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Werror=implicit-function-declaration" >&5 +$as_echo_n "checking if $CC accepts -Werror=implicit-function-declaration... " >&6; } + CFLAGS="-Werror=implicit-function-declaration $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Werror=declaration-after-statement" >&5 +$as_echo_n "checking if $CC accepts -Werror=declaration-after-statement... " >&6; } + CFLAGS="-Werror=declaration-after-statement $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wextra-tokens" >&5 +$as_echo_n "checking if $CC accepts -Wextra-tokens... " >&6; } + CFLAGS="-Wextra-tokens $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wnewline-eof" >&5 +$as_echo_n "checking if $CC accepts -Wnewline-eof... " >&6; } + CFLAGS="-Wnewline-eof $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wshorten-64-to-32" >&5 +$as_echo_n "checking if $CC accepts -Wshorten-64-to-32... " >&6; } + CFLAGS="-Wshorten-64-to-32 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wold-style-definition" >&5 +$as_echo_n "checking if $CC accepts -Wold-style-definition... " >&6; } + CFLAGS="-Wold-style-definition $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wno-system-headers" >&5 +$as_echo_n "checking if $CC accepts -Wno-system-headers... " >&6; } + CFLAGS="-Wno-system-headers $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wno-format-nonliteral" >&5 +$as_echo_n "checking if $CC accepts -Wno-format-nonliteral... " >&6; } + CFLAGS="-Wno-format-nonliteral $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + CMAINTAINERFLAGS="$CFLAGS $CMAINTAINERFLAGS" + CFLAGS="$CFLAGS_KEEP" + + CMAINTAINERFLAGS="-Wall -Wpointer-arith -Wwrite-strings -Wshadow -Wformat=2 -Wunused -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wno-multichar -Wredundant-decls -Wnested-externs -Winline -Wno-long-long $CMAINTAINERFLAGS" + fi + if test "$GXX" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: maintainer-mode: adding G++ warning flags" >&5 +$as_echo "$as_me: maintainer-mode: adding G++ warning flags" >&6;} + + CXXFLAGS_KEEP="$CXXFLAGS" + CXXFLAGS="" + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wextra-tokens" >&5 +$as_echo_n "checking if $CXX accepts -Wextra-tokens... " >&6; } + CXXFLAGS="-Wextra-tokens $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wnewline-eof" >&5 +$as_echo_n "checking if $CXX accepts -Wnewline-eof... " >&6; } + CXXFLAGS="-Wnewline-eof $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wshorten-64-to-32" >&5 +$as_echo_n "checking if $CXX accepts -Wshorten-64-to-32... " >&6; } + CXXFLAGS="-Wshorten-64-to-32 $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wno-system-headers" >&5 +$as_echo_n "checking if $CXX accepts -Wno-system-headers... " >&6; } + CXXFLAGS="-Wno-system-headers $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + CXXMAINTAINERFLAGS="$CXXFLAGS $CXXMAINTAINERFLAGS" + CXXFLAGS="$CXXFLAGS_KEEP" + + CXXMAINTAINERFLAGS="-Wall -Wpointer-arith -Wwrite-strings -Wshadow -Wunused -Wunreachable-code $CXXMAINTAINERFLAGS" + fi + fi + +fi + + +if test "$enable_debugging" = "yes" ; then + if test "$enable_optimization" != "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling optimizations for debugging" >&5 +$as_echo "$as_me: Disabling optimizations for debugging" >&6;} + CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`" + CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`" + fi + if test -z "`echo $CUSERFLAGS' ' | $EGREP -- '-g[0-9]? '`"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling debugging for C" >&5 +$as_echo "$as_me: Enabling debugging for C" >&6;} + CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`" + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -fno-inline" >&5 +$as_echo_n "checking if $CC accepts -fno-inline... " >&6; } + CFLAGS="-fno-inline $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -fno-omit-frame-pointer" >&5 +$as_echo_n "checking if $CC accepts -fno-omit-frame-pointer... " >&6; } + CFLAGS="-fno-omit-frame-pointer $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -g3" >&5 +$as_echo_n "checking if $CC accepts -g3... " >&6; } + CFLAGS="-g3 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -g2" >&5 +$as_echo_n "checking if $CC accepts -g2... " >&6; } + CFLAGS="-g2 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -g" >&5 +$as_echo_n "checking if $CC accepts -g... " >&6; } + CFLAGS="-g $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + fi + if test -z "`echo $CXXUSERFLAGS' ' | $EGREP -- '-g[0-9]? '`"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling debugging for C++" >&5 +$as_echo "$as_me: Enabling debugging for C++" >&6;} + CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`" + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -fno-inline" >&5 +$as_echo_n "checking if $CXX accepts -fno-inline... " >&6; } + CXXFLAGS="-fno-inline $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -fno-omit-frame-pointer" >&5 +$as_echo_n "checking if $CXX accepts -fno-omit-frame-pointer... " >&6; } + CXXFLAGS="-fno-omit-frame-pointer $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -g3" >&5 +$as_echo_n "checking if $CXX accepts -g3... " >&6; } + CXXFLAGS="-g3 $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -g2" >&5 +$as_echo_n "checking if $CXX accepts -g2... " >&6; } + CXXFLAGS="-g2 $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -g" >&5 +$as_echo_n "checking if $CXX accepts -g... " >&6; } + CXXFLAGS="-g $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + fi + CFLAGS="$CFLAGS -DSVN_DEBUG -DAP_DEBUG" + CXXFLAGS="$CXXFLAGS -DSVN_DEBUG -DAP_DEBUG" +elif test "$enable_debugging" = "no" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling debugging" >&5 +$as_echo "$as_me: Disabling debugging" >&6;} + CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`" + CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`" + CFLAGS="$CFLAGS -DNDEBUG" + CXXFLAGS="$CXXFLAGS -DNDEBUG" +# elif test "$enable_debugging" = "maybe" ; then +# # do nothing +fi + +if test "$enable_optimization" = "yes"; then + if test -z "`echo $CUSERFLAGS' ' | $EGREP -- '-O[^ ]* '`"; then + CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`" + if test "$enable_debugging" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling optimizations for C (with debugging enabled)" >&5 +$as_echo "$as_me: Enabling optimizations for C (with debugging enabled)" >&6;} + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O1" >&5 +$as_echo_n "checking if $CC accepts -O1... " >&6; } + CFLAGS="-O1 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O" >&5 +$as_echo_n "checking if $CC accepts -O... " >&6; } + CFLAGS="-O $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + else + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling optimizations for C" >&5 +$as_echo "$as_me: Enabling optimizations for C" >&6;} + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O3" >&5 +$as_echo_n "checking if $CC accepts -O3... " >&6; } + CFLAGS="-O3 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O2" >&5 +$as_echo_n "checking if $CC accepts -O2... " >&6; } + CFLAGS="-O2 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O1" >&5 +$as_echo_n "checking if $CC accepts -O1... " >&6; } + CFLAGS="-O1 $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O" >&5 +$as_echo_n "checking if $CC accepts -O... " >&6; } + CFLAGS="-O $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wno-clobbered" >&5 +$as_echo_n "checking if $CC accepts -Wno-clobbered... " >&6; } + CFLAGS="-Wno-clobbered $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -flto" >&5 +$as_echo_n "checking if $CC accepts -flto... " >&6; } + CFLAGS="-flto $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -fwhole-program" >&5 +$as_echo_n "checking if $CC accepts -fwhole-program... " >&6; } + CFLAGS="-fwhole-program $CFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + fi + fi + if test -z "`echo $CXXUSERFLAGS' ' | $EGREP -- '-O[^ ]* '`"; then + CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`" + if test "$enable_debugging" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling optimizations for C++ (with debugging enabled)" >&5 +$as_echo "$as_me: Enabling optimizations for C++ (with debugging enabled)" >&6;} + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O1" >&5 +$as_echo_n "checking if $CXX accepts -O1... " >&6; } + CXXFLAGS="-O1 $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O" >&5 +$as_echo_n "checking if $CXX accepts -O... " >&6; } + CXXFLAGS="-O $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + else + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling optimizations for C++" >&5 +$as_echo "$as_me: Enabling optimizations for C++" >&6;} + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O3" >&5 +$as_echo_n "checking if $CXX accepts -O3... " >&6; } + CXXFLAGS="-O3 $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O2" >&5 +$as_echo_n "checking if $CXX accepts -O2... " >&6; } + CXXFLAGS="-O2 $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O1" >&5 +$as_echo_n "checking if $CXX accepts -O1... " >&6; } + CXXFLAGS="-O1 $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O" >&5 +$as_echo_n "checking if $CXX accepts -O... " >&6; } + CXXFLAGS="-O $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wno-clobbered" >&5 +$as_echo_n "checking if $CXX accepts -Wno-clobbered... " >&6; } + CXXFLAGS="-Wno-clobbered $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -flto" >&5 +$as_echo_n "checking if $CXX accepts -flto... " >&6; } + CXXFLAGS="-flto $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + _svn_xxflags__save="$CXXFLAGS" + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -fwhole-program" >&5 +$as_echo_n "checking if $CXX accepts -fwhole-program... " >&6; } + CXXFLAGS="-fwhole-program $CXXFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$_svn_xxflags__save" + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + fi + fi +elif test "$enable_optimization" = "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling optimizations" >&5 +$as_echo "$as_me: Disabling optimizations" >&6;} + CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`" + CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`" +# elif test "$enable_optimization" = "maybe" ; then +# # do nothing +fi + + +# Check whether --enable-full-version-match was given. +if test "${enable_full_version_match+set}" = set; then : + enableval=$enable_full_version_match; + if test "$enableval" = "no" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling svn full version matching" >&5 +$as_echo "$as_me: Disabling svn full version matching" >&6;} + +$as_echo "#define SVN_DISABLE_FULL_VERSION_MATCH 1" >>confdefs.h + + fi + +fi + + + +# Check whether --with-editor was given. +if test "${with_editor+set}" = set; then : + withval=$with_editor; + + if test "$withval" = "yes" ; then + as_fn_error $? "--with-editor requires an argument." "$LINENO" 5 + else + SVN_CLIENT_EDITOR=$withval + +cat >>confdefs.h <<_ACEOF +#define SVN_CLIENT_EDITOR "$SVN_CLIENT_EDITOR" +_ACEOF + + + fi + + +fi + + + + zlib_found=no + + +# Check whether --with-zlib was given. +if test "${with_zlib+set}" = set; then : + withval=$with_zlib; + if test "$withval" = "yes" ; then + ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" +if test "x$ac_cv_header_zlib_h" = xyes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inflate in -lz" >&5 +$as_echo_n "checking for inflate in -lz... " >&6; } +if ${ac_cv_lib_z_inflate+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lz $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char inflate (); +int +main () +{ +return inflate (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_z_inflate=yes +else + ac_cv_lib_z_inflate=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_inflate" >&5 +$as_echo "$ac_cv_lib_z_inflate" >&6; } +if test "x$ac_cv_lib_z_inflate" = xyes; then : + zlib_found="builtin" +fi + + +fi + + + elif test "$withval" = "no" ; then + as_fn_error $? "cannot compile without zlib." "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: zlib library configuration" >&5 +$as_echo "$as_me: zlib library configuration" >&6;} + zlib_prefix=$withval + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS -I$zlib_prefix/include" + for ac_header in zlib.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" +if test "x$ac_cv_header_zlib_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_ZLIB_H 1 +_ACEOF + + save_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS -L$zlib_prefix/lib" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inflate in -lz" >&5 +$as_echo_n "checking for inflate in -lz... " >&6; } +if ${ac_cv_lib_z_inflate+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lz $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char inflate (); +int +main () +{ +return inflate (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_z_inflate=yes +else + ac_cv_lib_z_inflate=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_inflate" >&5 +$as_echo "$ac_cv_lib_z_inflate" >&6; } +if test "x$ac_cv_lib_z_inflate" = xyes; then : + zlib_found="yes" +fi + + LDFLAGS="$save_ldflags" + +fi + +done + + CPPFLAGS="$save_cppflags" + fi + +else + + ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" +if test "x$ac_cv_header_zlib_h" = xyes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inflate in -lz" >&5 +$as_echo_n "checking for inflate in -lz... " >&6; } +if ${ac_cv_lib_z_inflate+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lz $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char inflate (); +int +main () +{ +return inflate (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_z_inflate=yes +else + ac_cv_lib_z_inflate=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_inflate" >&5 +$as_echo "$ac_cv_lib_z_inflate" >&6; } +if test "x$ac_cv_lib_z_inflate" = xyes; then : + zlib_found="builtin" +fi + + +fi + + + +fi + + + if test "$zlib_found" = "no"; then + as_fn_error $? "subversion requires zlib" "$LINENO" 5 + fi + + if test "$zlib_found" = "yes"; then + SVN_ZLIB_INCLUDES="-I$zlib_prefix/include" + LDFLAGS="$LDFLAGS ` + input_flags="-L$zlib_prefix/lib" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + fi + + SVN_ZLIB_LIBS="-lz" + + + + + +MOD_ACTIVATION="" +# Check whether --enable-mod-activation was given. +if test "${enable_mod_activation+set}" = set; then : + enableval=$enable_mod_activation; + if test "$enableval" = "yes" ; then + MOD_ACTIVATION="-a" + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling apache module activation" >&5 +$as_echo "$as_me: Enabling apache module activation" >&6;} + else + { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling apache module activation" >&5 +$as_echo "$as_me: Disabling apache module activation" >&6;} + fi + +fi + + + + + +# Check whether --enable-gcov was given. +if test "${enable_gcov+set}" = set; then : + enableval=$enable_gcov; + if test "$enableval" = "yes" ; then + if test "$GCC" = "yes"; then + if test "$svn_enable_shared" = "yes" ; then + as_fn_error $? "Can't have --enable-gcov without --disable-shared (we + recommend also using --enable-all-static)." "$LINENO" 5 + fi + if test ! "$enable_all_static" = "yes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: We recommend --enable-all-static with --enable-gcov." >&5 +$as_echo "$as_me: WARNING: We recommend --enable-all-static with --enable-gcov." >&2;} + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling gcov coverage testing." >&5 +$as_echo "$as_me: Enabling gcov coverage testing." >&6;} + CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage" + CXXFLAGS="$CXXFLAGS -fprofile-arcs -ftest-coverage" + else + as_fn_error $? "We only support --enable-gcov with GCC right now." "$LINENO" 5 + fi + fi + +fi + + +# Check whether --enable-gprof was given. +if test "${enable_gprof+set}" = set; then : + enableval=$enable_gprof; + if test "$enableval" = "yes" ; then + if test "$GCC" = "yes"; then + if test "$svn_enable_shared" = "yes" ; then + as_fn_error $? "Can't have --enable-gprof without --disable-shared (we + recommend also using --enable-all-static)." "$LINENO" 5 + fi + if test ! "$enable_all_static" = "yes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: We recommend --enable-all-static with --enable-gprof." >&5 +$as_echo "$as_me: WARNING: We recommend --enable-all-static with --enable-gprof." >&2;} + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling gprof profiling data (to gmon.out)." >&5 +$as_echo "$as_me: Enabling gprof profiling data (to gmon.out)." >&6;} + CFLAGS="$CFLAGS -pg" + CXXFLAGS="$CXXFLAGS -pg" + LT_LDFLAGS="$LT_LDFLAGS -pg" + else + as_fn_error $? "We only support --enable-gprof with GCC right now." "$LINENO" 5 + fi + fi + +fi + + + +# Scripting and Bindings languages + +# Python: Used for testsuite, and bindings + + +PYTHON="`$abs_srcdir/build/find_python.sh`" +if test -z "$PYTHON"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Python 2.5 or later is required to run the testsuite" >&5 +$as_echo "$as_me: WARNING: Python 2.5 or later is required to run the testsuite" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: or to use the Subversion Python bindings" >&5 +$as_echo "$as_me: WARNING: or to use the Subversion Python bindings" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: " >&5 +$as_echo "$as_me: WARNING: " >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: If you have a suitable Python installed, but not on the" >&5 +$as_echo "$as_me: WARNING: If you have a suitable Python installed, but not on the" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: PATH, set the environment variable PYTHON to the full path" >&5 +$as_echo "$as_me: WARNING: PATH, set the environment variable PYTHON to the full path" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: to the Python executable, and re-run configure" >&5 +$as_echo "$as_me: WARNING: to the Python executable, and re-run configure" >&2;} +fi +for ac_prog in "$PYTHON" +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PYTHON+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PYTHON in + [\\/]* | ?:[\\/]*) + ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PYTHON=$ac_cv_path_PYTHON +if test -n "$PYTHON"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 +$as_echo "$PYTHON" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$PYTHON" && break +done +test -n "$PYTHON" || PYTHON="none" + + +# The minimum version for the JVM runtime for our Java bytecode. +JAVA_OLDEST_WORKING_VER='1.5' +# SVN_CHECK_JDK sets $JAVA_CLASSPATH + + JAVA_OLDEST_WORKING_VER="$JAVA_OLDEST_WORKING_VER" + +# Check whether --with-jdk was given. +if test "${with_jdk+set}" = set; then : + withval=$with_jdk; + case "$withval" in + "no") + JDK_SUITABLE=no + ;; + "yes") + + where=check + JAVA_OLDEST_WORKING_VER="$JAVA_OLDEST_WORKING_VER" + + JDK=none + JAVA_BIN=none + JAVADOC=none + JAVAC=none + JAVAH=none + JAR=none + JNI_INCLUDES=none + + JDK_SUITABLE=no + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for JDK" >&5 +$as_echo_n "checking for JDK... " >&6; } + if test $where = check; then + if test -x "$JAVA_HOME/bin/java"; then + JDK="$JAVA_HOME" + elif test -x "/Library/Java/Home/bin/java"; then + JDK="/Library/Java/Home" + elif test -x "/usr/bin/java"; then + JDK="/usr" + elif test -x "/usr/local/bin/java"; then + JDK="/usr/local" + fi + else + JDK=$where + fi + + os_arch="`uname`" + if test "$os_arch" = "Darwin"; then + OSX_VER=`/usr/bin/sw_vers | grep ProductVersion | cut -f2 | cut -d"." -f1,2` + + if test "$OSX_VER" = "10.4"; then + OSX_VER="10.4u" + fi + + OSX_SYS_JAVA_FRAMEWORK="/System/Library/Frameworks/JavaVM.framework" + OSX_SDK_JAVA_FRAMEWORK="/Developer/SDKs/MacOSX$OSX_VER.sdk/System/Library" + OSX_SDK_JAVA_FRAMEWORK="$OSX_SDK_JAVA_FRAMEWORK/Frameworks/JavaVM.framework" + fi + + if test "$os_arch" = "Darwin" && test "$JDK" = "/usr" && + test -d "/Library/Java/Home"; then + JDK="/Library/Java/Home" + fi + + if test "$os_arch" = "Darwin" && test "$JDK" = "/Library/Java/Home"; then + JRE_LIB_DIR="$OSX_SYS_JAVA_FRAMEWORK/Classes" + else + JRE_LIB_DIR="$JDK/jre/lib" + fi + + if test -f "$JDK/include/jni.h"; then + JNI_INCLUDEDIR="$JDK/include" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && test -e "$JDK/Headers/jni.h"; then + JNI_INCLUDEDIR="$JDK/Headers" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && + test -e "$OSX_SYS_JAVA_FRAMEWORK/Headers/jni.h"; then + JNI_INCLUDEDIR="$OSX_SYS_JAVA_FRAMEWORK/Headers" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && + test -e "$OSX_SDK_JAVA_FRAMEWORK/Headers/jni.h"; then + JNI_INCLUDEDIR="$OSX_SDK_JAVA_FRAMEWORK/Headers" + JDK_SUITABLE=yes + else + JDK_SUITABLE=no + fi + if test "$JDK_SUITABLE" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $JNI_INCLUDEDIR/jni.h" >&5 +$as_echo "$JNI_INCLUDEDIR/jni.h" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + if test "$where" != "check"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: no JNI header files found." >&5 +$as_echo "$as_me: WARNING: no JNI header files found." >&2;} + if test "$os_arch" = "Darwin"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&5 +$as_echo "$as_me: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&2;} + fi + fi + fi + + if test "$JDK_SUITABLE" = "yes"; then + JAVA_BIN='$(JDK)/bin' + + JAVA="$JAVA_BIN/java" + JAVAC="$JAVA_BIN/javac" + JAVAH="$JAVA_BIN/javah" + JAVADOC="$JAVA_BIN/javadoc" + JAR="$JAVA_BIN/jar" + + jikes_options="/usr/local/bin/jikes /usr/bin/jikes" + +# Check whether --with-jikes was given. +if test "${with_jikes+set}" = set; then : + withval=$with_jikes; + if test "$withval" != "no" && test "$withval" != "yes"; then + jikes_options="$withval $jikes_options" + fi + requested_jikes="$withval" # will be 'yes' if path unspecified + +fi + + if test "$requested_jikes" != "no"; then + for jikes in $jikes_options; do + if test -z "$jikes_found" && test -x "$jikes"; then + jikes_found="yes" + JAVAC="$jikes" + JAVA_CLASSPATH="$JRE_LIB_DIR" + for jar in $JRE_LIB_DIR/*.jar; do + JAVA_CLASSPATH="$JAVA_CLASSPATH:$jar" + done + fi + done + fi + if test -n "$requested_jikes" && test "$requested_jikes" != "no"; then + if test -z "$jikes_found"; then + as_fn_error $? "Could not find a usable version of Jikes" "$LINENO" 5 + elif test -n "$jikes_found" && test "$requested_jikes" != "yes" && + test "$JAVAC" != "$requested_jikes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-jikes PATH was invalid, substitute found" >&5 +$as_echo "$as_me: WARNING: --with-jikes PATH was invalid, substitute found" >&2;} + fi + fi + + # The release for "-source" could actually be greater than that + # of "-target", if we want to cross-compile for lesser JVMs. + if test -z "$JAVAC_FLAGS"; then + JAVAC_FLAGS="-target $JAVA_OLDEST_WORKING_VER -source 1.5" + if test "$enable_debugging" = "yes"; then + JAVAC_FLAGS="-g -Xlint:unchecked $JAVAC_FLAGS" + fi + fi + + JNI_INCLUDES="-I$JNI_INCLUDEDIR" + list="`find "$JNI_INCLUDEDIR" -type d -print`" + for dir in $list; do + JNI_INCLUDES="$JNI_INCLUDES -I$dir" + done + fi + + + + + + + + + + + ;; + *) + + where=$withval + JAVA_OLDEST_WORKING_VER="$JAVA_OLDEST_WORKING_VER" + + JDK=none + JAVA_BIN=none + JAVADOC=none + JAVAC=none + JAVAH=none + JAR=none + JNI_INCLUDES=none + + JDK_SUITABLE=no + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for JDK" >&5 +$as_echo_n "checking for JDK... " >&6; } + if test $where = check; then + if test -x "$JAVA_HOME/bin/java"; then + JDK="$JAVA_HOME" + elif test -x "/Library/Java/Home/bin/java"; then + JDK="/Library/Java/Home" + elif test -x "/usr/bin/java"; then + JDK="/usr" + elif test -x "/usr/local/bin/java"; then + JDK="/usr/local" + fi + else + JDK=$where + fi + + os_arch="`uname`" + if test "$os_arch" = "Darwin"; then + OSX_VER=`/usr/bin/sw_vers | grep ProductVersion | cut -f2 | cut -d"." -f1,2` + + if test "$OSX_VER" = "10.4"; then + OSX_VER="10.4u" + fi + + OSX_SYS_JAVA_FRAMEWORK="/System/Library/Frameworks/JavaVM.framework" + OSX_SDK_JAVA_FRAMEWORK="/Developer/SDKs/MacOSX$OSX_VER.sdk/System/Library" + OSX_SDK_JAVA_FRAMEWORK="$OSX_SDK_JAVA_FRAMEWORK/Frameworks/JavaVM.framework" + fi + + if test "$os_arch" = "Darwin" && test "$JDK" = "/usr" && + test -d "/Library/Java/Home"; then + JDK="/Library/Java/Home" + fi + + if test "$os_arch" = "Darwin" && test "$JDK" = "/Library/Java/Home"; then + JRE_LIB_DIR="$OSX_SYS_JAVA_FRAMEWORK/Classes" + else + JRE_LIB_DIR="$JDK/jre/lib" + fi + + if test -f "$JDK/include/jni.h"; then + JNI_INCLUDEDIR="$JDK/include" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && test -e "$JDK/Headers/jni.h"; then + JNI_INCLUDEDIR="$JDK/Headers" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && + test -e "$OSX_SYS_JAVA_FRAMEWORK/Headers/jni.h"; then + JNI_INCLUDEDIR="$OSX_SYS_JAVA_FRAMEWORK/Headers" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && + test -e "$OSX_SDK_JAVA_FRAMEWORK/Headers/jni.h"; then + JNI_INCLUDEDIR="$OSX_SDK_JAVA_FRAMEWORK/Headers" + JDK_SUITABLE=yes + else + JDK_SUITABLE=no + fi + if test "$JDK_SUITABLE" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $JNI_INCLUDEDIR/jni.h" >&5 +$as_echo "$JNI_INCLUDEDIR/jni.h" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + if test "$where" != "check"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: no JNI header files found." >&5 +$as_echo "$as_me: WARNING: no JNI header files found." >&2;} + if test "$os_arch" = "Darwin"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&5 +$as_echo "$as_me: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&2;} + fi + fi + fi + + if test "$JDK_SUITABLE" = "yes"; then + JAVA_BIN='$(JDK)/bin' + + JAVA="$JAVA_BIN/java" + JAVAC="$JAVA_BIN/javac" + JAVAH="$JAVA_BIN/javah" + JAVADOC="$JAVA_BIN/javadoc" + JAR="$JAVA_BIN/jar" + + jikes_options="/usr/local/bin/jikes /usr/bin/jikes" + +# Check whether --with-jikes was given. +if test "${with_jikes+set}" = set; then : + withval=$with_jikes; + if test "$withval" != "no" && test "$withval" != "yes"; then + jikes_options="$withval $jikes_options" + fi + requested_jikes="$withval" # will be 'yes' if path unspecified + +fi + + if test "$requested_jikes" != "no"; then + for jikes in $jikes_options; do + if test -z "$jikes_found" && test -x "$jikes"; then + jikes_found="yes" + JAVAC="$jikes" + JAVA_CLASSPATH="$JRE_LIB_DIR" + for jar in $JRE_LIB_DIR/*.jar; do + JAVA_CLASSPATH="$JAVA_CLASSPATH:$jar" + done + fi + done + fi + if test -n "$requested_jikes" && test "$requested_jikes" != "no"; then + if test -z "$jikes_found"; then + as_fn_error $? "Could not find a usable version of Jikes" "$LINENO" 5 + elif test -n "$jikes_found" && test "$requested_jikes" != "yes" && + test "$JAVAC" != "$requested_jikes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-jikes PATH was invalid, substitute found" >&5 +$as_echo "$as_me: WARNING: --with-jikes PATH was invalid, substitute found" >&2;} + fi + fi + + # The release for "-source" could actually be greater than that + # of "-target", if we want to cross-compile for lesser JVMs. + if test -z "$JAVAC_FLAGS"; then + JAVAC_FLAGS="-target $JAVA_OLDEST_WORKING_VER -source 1.5" + if test "$enable_debugging" = "yes"; then + JAVAC_FLAGS="-g -Xlint:unchecked $JAVAC_FLAGS" + fi + fi + + JNI_INCLUDES="-I$JNI_INCLUDEDIR" + list="`find "$JNI_INCLUDEDIR" -type d -print`" + for dir in $list; do + JNI_INCLUDES="$JNI_INCLUDES -I$dir" + done + fi + + + + + + + + + + + ;; + esac + +else + + + where=check + JAVA_OLDEST_WORKING_VER="$JAVA_OLDEST_WORKING_VER" + + JDK=none + JAVA_BIN=none + JAVADOC=none + JAVAC=none + JAVAH=none + JAR=none + JNI_INCLUDES=none + + JDK_SUITABLE=no + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for JDK" >&5 +$as_echo_n "checking for JDK... " >&6; } + if test $where = check; then + if test -x "$JAVA_HOME/bin/java"; then + JDK="$JAVA_HOME" + elif test -x "/Library/Java/Home/bin/java"; then + JDK="/Library/Java/Home" + elif test -x "/usr/bin/java"; then + JDK="/usr" + elif test -x "/usr/local/bin/java"; then + JDK="/usr/local" + fi + else + JDK=$where + fi + + os_arch="`uname`" + if test "$os_arch" = "Darwin"; then + OSX_VER=`/usr/bin/sw_vers | grep ProductVersion | cut -f2 | cut -d"." -f1,2` + + if test "$OSX_VER" = "10.4"; then + OSX_VER="10.4u" + fi + + OSX_SYS_JAVA_FRAMEWORK="/System/Library/Frameworks/JavaVM.framework" + OSX_SDK_JAVA_FRAMEWORK="/Developer/SDKs/MacOSX$OSX_VER.sdk/System/Library" + OSX_SDK_JAVA_FRAMEWORK="$OSX_SDK_JAVA_FRAMEWORK/Frameworks/JavaVM.framework" + fi + + if test "$os_arch" = "Darwin" && test "$JDK" = "/usr" && + test -d "/Library/Java/Home"; then + JDK="/Library/Java/Home" + fi + + if test "$os_arch" = "Darwin" && test "$JDK" = "/Library/Java/Home"; then + JRE_LIB_DIR="$OSX_SYS_JAVA_FRAMEWORK/Classes" + else + JRE_LIB_DIR="$JDK/jre/lib" + fi + + if test -f "$JDK/include/jni.h"; then + JNI_INCLUDEDIR="$JDK/include" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && test -e "$JDK/Headers/jni.h"; then + JNI_INCLUDEDIR="$JDK/Headers" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && + test -e "$OSX_SYS_JAVA_FRAMEWORK/Headers/jni.h"; then + JNI_INCLUDEDIR="$OSX_SYS_JAVA_FRAMEWORK/Headers" + JDK_SUITABLE=yes + elif test "$os_arch" = "Darwin" && + test -e "$OSX_SDK_JAVA_FRAMEWORK/Headers/jni.h"; then + JNI_INCLUDEDIR="$OSX_SDK_JAVA_FRAMEWORK/Headers" + JDK_SUITABLE=yes + else + JDK_SUITABLE=no + fi + if test "$JDK_SUITABLE" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $JNI_INCLUDEDIR/jni.h" >&5 +$as_echo "$JNI_INCLUDEDIR/jni.h" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + if test "$where" != "check"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: no JNI header files found." >&5 +$as_echo "$as_me: WARNING: no JNI header files found." >&2;} + if test "$os_arch" = "Darwin"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&5 +$as_echo "$as_me: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&2;} + fi + fi + fi + + if test "$JDK_SUITABLE" = "yes"; then + JAVA_BIN='$(JDK)/bin' + + JAVA="$JAVA_BIN/java" + JAVAC="$JAVA_BIN/javac" + JAVAH="$JAVA_BIN/javah" + JAVADOC="$JAVA_BIN/javadoc" + JAR="$JAVA_BIN/jar" + + jikes_options="/usr/local/bin/jikes /usr/bin/jikes" + +# Check whether --with-jikes was given. +if test "${with_jikes+set}" = set; then : + withval=$with_jikes; + if test "$withval" != "no" && test "$withval" != "yes"; then + jikes_options="$withval $jikes_options" + fi + requested_jikes="$withval" # will be 'yes' if path unspecified + +fi + + if test "$requested_jikes" != "no"; then + for jikes in $jikes_options; do + if test -z "$jikes_found" && test -x "$jikes"; then + jikes_found="yes" + JAVAC="$jikes" + JAVA_CLASSPATH="$JRE_LIB_DIR" + for jar in $JRE_LIB_DIR/*.jar; do + JAVA_CLASSPATH="$JAVA_CLASSPATH:$jar" + done + fi + done + fi + if test -n "$requested_jikes" && test "$requested_jikes" != "no"; then + if test -z "$jikes_found"; then + as_fn_error $? "Could not find a usable version of Jikes" "$LINENO" 5 + elif test -n "$jikes_found" && test "$requested_jikes" != "yes" && + test "$JAVAC" != "$requested_jikes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-jikes PATH was invalid, substitute found" >&5 +$as_echo "$as_me: WARNING: --with-jikes PATH was invalid, substitute found" >&2;} + fi + fi + + # The release for "-source" could actually be greater than that + # of "-target", if we want to cross-compile for lesser JVMs. + if test -z "$JAVAC_FLAGS"; then + JAVAC_FLAGS="-target $JAVA_OLDEST_WORKING_VER -source 1.5" + if test "$enable_debugging" = "yes"; then + JAVAC_FLAGS="-g -Xlint:unchecked $JAVAC_FLAGS" + fi + fi + + JNI_INCLUDES="-I$JNI_INCLUDEDIR" + list="`find "$JNI_INCLUDEDIR" -type d -print`" + for dir in $list; do + JNI_INCLUDES="$JNI_INCLUDES -I$dir" + done + fi + + + + + + + + + + + +fi + + + +# Extract the first word of "perl", so it can be a program name with args. +set dummy perl; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PERL+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PERL in + [\\/]* | ?:[\\/]*) + ac_cv_path_PERL="$PERL" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_PERL" && ac_cv_path_PERL="none" + ;; +esac +fi +PERL=$ac_cv_path_PERL +if test -n "$PERL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL" >&5 +$as_echo "$PERL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + +if test -n "$RUBY"; then + # Extract the first word of ""$RUBY"", so it can be a program name with args. +set dummy "$RUBY"; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_RUBY+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $RUBY in + [\\/]* | ?:[\\/]*) + ac_cv_path_RUBY="$RUBY" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_RUBY="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_RUBY" && ac_cv_path_RUBY="none" + ;; +esac +fi +RUBY=$ac_cv_path_RUBY +if test -n "$RUBY"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RUBY" >&5 +$as_echo "$RUBY" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +else + for ac_prog in ruby ruby1.8 ruby18 ruby1.9 ruby1 ruby1.9.3 ruby193 +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_RUBY+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $RUBY in + [\\/]* | ?:[\\/]*) + ac_cv_path_RUBY="$RUBY" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_RUBY="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +RUBY=$ac_cv_path_RUBY +if test -n "$RUBY"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RUBY" >&5 +$as_echo "$RUBY" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$RUBY" && break +done +test -n "$RUBY" || RUBY="none" + +fi +if test "$RUBY" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking rb_hash_foreach" >&5 +$as_echo_n "checking rb_hash_foreach... " >&6; } + if "$RUBY" -r mkmf -e 'exit(have_func("rb_hash_foreach") ? 0 : 1)' >/dev/null; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + if test -n "$RDOC"; then + # Extract the first word of ""$RDOC"", so it can be a program name with args. +set dummy "$RDOC"; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_RDOC+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $RDOC in + [\\/]* | ?:[\\/]*) + ac_cv_path_RDOC="$RDOC" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_RDOC="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_RDOC" && ac_cv_path_RDOC="none" + ;; +esac +fi +RDOC=$ac_cv_path_RDOC +if test -n "$RDOC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RDOC" >&5 +$as_echo "$RDOC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + for ac_prog in rdoc rdoc1.8 rdoc18 rdoc1.9 rdoc19 rdoc1.9.3 rdoc193 +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_RDOC+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $RDOC in + [\\/]* | ?:[\\/]*) + ac_cv_path_RDOC="$RDOC" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_RDOC="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +RDOC=$ac_cv_path_RDOC +if test -n "$RDOC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RDOC" >&5 +$as_echo "$RDOC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$RDOC" && break +done +test -n "$RDOC" || RDOC="none" + + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby major version" >&5 +$as_echo_n "checking for Ruby major version... " >&6; } +if ${svn_cv_ruby_major+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_major="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MAJOR))'`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_major" >&5 +$as_echo "$svn_cv_ruby_major" >&6; } + RUBY_MAJOR="$svn_cv_ruby_major" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby minor version" >&5 +$as_echo_n "checking for Ruby minor version... " >&6; } +if ${svn_cv_ruby_minor+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_minor="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MINOR))'`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_minor" >&5 +$as_echo "$svn_cv_ruby_minor" >&6; } + RUBY_MINOR="$svn_cv_ruby_minor" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby teeny version" >&5 +$as_echo_n "checking for Ruby teeny version... " >&6; } +if ${svn_cv_ruby_teeny+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_teeny="`$RUBY -rrbconfig -e 'major, minor, teeny = RUBY_VERSION.split("."); print teeny;'`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_teeny" >&5 +$as_echo "$svn_cv_ruby_teeny" >&6; } + RUBY_TEENY="$svn_cv_ruby_teeny" + + + + + if test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -gt "8" -a "$RUBY_TEENY" -lt "3" \); then + # Disallow Ruby between 1.8.7 and 1.9.3 + RUBY="none" + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: The detected Ruby is between 1.9 and 1.9.3" >&5 +$as_echo "$as_me: WARNING: The detected Ruby is between 1.9 and 1.9.3" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Only 1.8.x and 1.9.3 releases are supported at this time" >&5 +$as_echo "$as_me: WARNING: Only 1.8.x and 1.9.3 releases are supported at this time" >&2;} + elif test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -eq "9" -a "$RUBY_TEENY" -eq "3" \); then + #Warn about 1.9.3 support + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: WARNING: The detected Ruby is 1.9.3" >&5 +$as_echo "$as_me: WARNING: WARNING: The detected Ruby is 1.9.3" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: WARNING: Only 1.8.x releases are fully supported, 1.9.3 support is new" >&5 +$as_echo "$as_me: WARNING: WARNING: Only 1.8.x releases are fully supported, 1.9.3 support is new" >&2;} + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + RUBY="none" + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: The detected Ruby is too old for Subversion to use" >&5 +$as_echo "$as_me: WARNING: The detected Ruby is too old for Subversion to use" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: A Ruby which has rb_hash_foreach is required to use the" >&5 +$as_echo "$as_me: WARNING: A Ruby which has rb_hash_foreach is required to use the" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion Ruby bindings" >&5 +$as_echo "$as_me: WARNING: Subversion Ruby bindings" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Upgrade to the official 1.8.2 release, or later" >&5 +$as_echo "$as_me: WARNING: Upgrade to the official 1.8.2 release, or later" >&2;} + fi +fi + + + +# Check whether --with-swig was given. +if test "${with_swig+set}" = set; then : + withval=$with_swig; + case "$withval" in + "no") + SWIG_SUITABLE=no + + where=no + + if test $where = no; then + SWIG=none + elif test $where = check; then + # Extract the first word of "swig", so it can be a program name with args. +set dummy swig; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_SWIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $SWIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_SWIG="$SWIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_SWIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_SWIG" && ac_cv_path_SWIG="none" + ;; +esac +fi +SWIG=$ac_cv_path_SWIG +if test -n "$SWIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG" >&5 +$as_echo "$SWIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + if test -f "$where"; then + SWIG="$where" + else + SWIG="$where/bin/swig" + fi + if test ! -f "$SWIG" || test ! -x "$SWIG"; then + as_fn_error $? "Could not find swig binary at $SWIG" "$LINENO" 5 + fi + fi + + if test "$SWIG" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking swig version" >&5 +$as_echo_n "checking swig version... " >&6; } + SWIG_VERSION_RAW="`$SWIG -version 2>&1 | \ + $SED -ne 's/^.*Version \(.*\)$/\1/p'`" + # We want the version as an integer so we can test against + # which version we're using. SWIG doesn't provide this + # to us so we have to come up with it on our own. + # The major is passed straight through, + # the minor is zero padded to two places, + # and the patch level is zero padded to three places. + # e.g. 1.3.24 becomes 103024 + SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \ + $SED -e 's/[^0-9\.].*$//' \ + -e 's/\.\([0-9]\)$/.0\1/' \ + -e 's/\.\([0-9][0-9]\)$/.0\1/' \ + -e 's/\.\([0-9]\)\./0\1/; s/\.//g;'`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_VERSION_RAW" >&5 +$as_echo "$SWIG_VERSION_RAW" >&6; } + # If you change the required swig version number, don't forget to update: + # subversion/bindings/swig/INSTALL + # packages/rpm/redhat-8+/subversion.spec + # packages/rpm/redhat-7.x/subversion.spec + # packages/rpm/rhel-3/subversion.spec + # packages/rpm/rhel-4/subversion.spec + if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then + SWIG_SUITABLE=yes + else + SWIG_SUITABLE=no + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&5 +$as_echo "$as_me: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion requires SWIG 1.3.24 or later" >&5 +$as_echo "$as_me: WARNING: Subversion requires SWIG 1.3.24 or later" >&2;} + fi + fi + + SWIG_PY_COMPILE="none" + SWIG_PY_LINK="none" + if test "$PYTHON" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring python swig binding" >&5 +$as_echo "$as_me: Configuring python swig binding" >&6;} + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python includes" >&5 +$as_echo_n "checking for Python includes... " >&6; } +if ${ac_cv_python_includes+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_includes" >&5 +$as_echo "$ac_cv_python_includes" >&6; } + SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes" + + if test "$ac_cv_python_includes" = "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python bindings cannot be built without distutils module" >&5 +$as_echo "$as_me: WARNING: python bindings cannot be built without distutils module" >&2;} + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiling Python extensions" >&5 +$as_echo_n "checking for compiling Python extensions... " >&6; } +if ${ac_cv_python_compile+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_compile" >&5 +$as_echo "$ac_cv_python_compile" >&6; } + SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python extensions" >&5 +$as_echo_n "checking for linking Python extensions... " >&6; } +if ${ac_cv_python_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_link" >&5 +$as_echo "$ac_cv_python_link" >&6; } + SWIG_PY_LINK="$ac_cv_python_link" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python libraries" >&5 +$as_echo_n "checking for linking Python libraries... " >&6; } +if ${ac_cv_python_libs+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_libs" >&5 +$as_echo "$ac_cv_python_libs" >&6; } + SWIG_PY_LIBS="` + input_flags="$ac_cv_python_libs" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + SVN_PYCFMT_SAVE_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_int64_t Python/C API format string" >&5 +$as_echo_n "checking for apr_int64_t Python/C API format string... " >&6; } +if ${svn_cv_pycfmt_apr_int64_t+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"lld\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="L" +fi +rm -f conftest* + + fi + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +r + #include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"ld\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="l" +fi +rm -f conftest* + + fi + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"d\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="i" +fi +rm -f conftest* + + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_pycfmt_apr_int64_t" >&5 +$as_echo "$svn_cv_pycfmt_apr_int64_t" >&6; } + CPPFLAGS="$SVN_PYCFMT_SAVE_CPPFLAGS" + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + as_fn_error $? "failed to recognize APR_INT64_T_FMT on this platform" "$LINENO" 5 + fi + +cat >>confdefs.h <<_ACEOF +#define SVN_APR_INT64_T_PYCFMT "$svn_cv_pycfmt_apr_int64_t" +_ACEOF + + fi + + if test "$PERL" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking perl version" >&5 +$as_echo_n "checking perl version... " >&6; } + PERL_VERSION="`$PERL -e 'q([); print $] * 1000000,$/;'`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL_VERSION" >&5 +$as_echo "$PERL_VERSION" >&6; } + if test "$PERL_VERSION" -ge "5008000"; then + SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: perl bindings require perl 5.8.0 or newer." >&5 +$as_echo "$as_me: WARNING: perl bindings require perl 5.8.0 or newer." >&2;} + fi + fi + + SWIG_RB_COMPILE="none" + SWIG_RB_LINK="none" + if test "$RUBY" != "none"; then + rbconfig="$RUBY -rrbconfig -e " + + for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \ + rubyhdrdir sitedir sitelibdir sitearchdir libdir + do + rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG['$var_name']"` + eval "rbconfig_$var_name=\"$rbconfig_tmp\"" + done + + { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ruby SWIG binding" >&5 +$as_echo "$as_me: Configuring Ruby SWIG binding" >&6;} + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby include path" >&5 +$as_echo_n "checking for Ruby include path... " >&6; } +if ${svn_cv_ruby_includes+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test -d "$rbconfig_rubyhdrdir"; then + svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir -I$rbconfig_rubyhdrdir/ruby -I$rbconfig_rubyhdrdir/ruby/backward -I$rbconfig_rubyhdrdir/$rbconfig_arch" + else + svn_cv_ruby_includes="-I. -I$rbconfig_archdir" + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_includes" >&5 +$as_echo "$svn_cv_ruby_includes" >&6; } + SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile Ruby extensions" >&5 +$as_echo_n "checking how to compile Ruby extensions... " >&6; } +if ${svn_cv_ruby_compile+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_compile="$rbconfig_CC $CFLAGS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_compile" >&5 +$as_echo "$svn_cv_ruby_compile" >&6; } + SWIG_RB_COMPILE="$svn_cv_ruby_compile" + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-ansi//'` + + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c89//'` + + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c90//'` + + SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby extensions" >&5 +$as_echo_n "checking how to link Ruby extensions... " >&6; } +if ${svn_cv_ruby_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \ + $rbconfig_LDSHARED`" + svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link" + svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_link" >&5 +$as_echo "$svn_cv_ruby_link" >&6; } + SWIG_RB_LINK="$svn_cv_ruby_link" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby libraries" >&5 +$as_echo_n "checking how to link Ruby libraries... " >&6; } +if ${ac_cv_ruby_libs+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ruby_libs" >&5 +$as_echo "$ac_cv_ruby_libs" >&6; } + SWIG_RB_LIBS="` + input_flags="$ac_cv_ruby_libs" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rb_errinfo" >&5 +$as_echo_n "checking for rb_errinfo... " >&6; } + old_CFLAGS="$CFLAGS" + old_LIBS="$LIBS" + CFLAGS="$CFLAGS $svn_cv_ruby_includes" + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-ansi//'` + + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c89//'` + + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c90//'` + + LIBS="$SWIG_RB_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +int main() +{rb_errinfo();} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + have_rb_errinfo="yes" +else + have_rb_errinfo="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test "$have_rb_errinfo" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define HAVE_RB_ERRINFO 1" >>confdefs.h + + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + CFLAGS="$old_CFLAGS" + LIBS="$old_LIBS" + + if ${svn_cv_ruby_sitedir+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir="$rbconfig_sitedir" + +fi + + +# Check whether --with-ruby-sitedir was given. +if test "${with_ruby_sitedir+set}" = set; then : + withval=$with_ruby_sitedir; svn_ruby_installdir="$withval" +else + svn_ruby_installdir="$svn_cv_ruby_sitedir" +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby scripts" >&5 +$as_echo_n "checking where to install Ruby scripts... " >&6; } + if ${svn_cv_ruby_sitedir_libsuffix+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + +fi + + SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_LIB_DIR" >&5 +$as_echo "$SWIG_RB_SITE_LIB_DIR" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby extensions" >&5 +$as_echo_n "checking where to install Ruby extensions... " >&6; } + if ${svn_cv_ruby_sitedir_archsuffix+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + +fi + + SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_ARCH_DIR" >&5 +$as_echo "$SWIG_RB_SITE_ARCH_DIR" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to use output level for Ruby bindings tests" >&5 +$as_echo_n "checking how to use output level for Ruby bindings tests... " >&6; } + if ${svn_cv_ruby_test_verbose+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_test_verbose="normal" + +fi + + +# Check whether --with-ruby-test-verbose was given. +if test "${with_ruby_test_verbose+set}" = set; then : + withval=$with_ruby_test_verbose; svn_ruby_test_verbose="$withval" +else + svn_ruby_test_verbose="$svn_cv_ruby_test_verbose" +fi + + SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_TEST_VERBOSE" >&5 +$as_echo "$SWIG_RB_TEST_VERBOSE" >&6; } + fi + + + + + + + + + + + + + + + ;; + "yes") + + where=check + + if test $where = no; then + SWIG=none + elif test $where = check; then + # Extract the first word of "swig", so it can be a program name with args. +set dummy swig; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_SWIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $SWIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_SWIG="$SWIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_SWIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_SWIG" && ac_cv_path_SWIG="none" + ;; +esac +fi +SWIG=$ac_cv_path_SWIG +if test -n "$SWIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG" >&5 +$as_echo "$SWIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + if test -f "$where"; then + SWIG="$where" + else + SWIG="$where/bin/swig" + fi + if test ! -f "$SWIG" || test ! -x "$SWIG"; then + as_fn_error $? "Could not find swig binary at $SWIG" "$LINENO" 5 + fi + fi + + if test "$SWIG" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking swig version" >&5 +$as_echo_n "checking swig version... " >&6; } + SWIG_VERSION_RAW="`$SWIG -version 2>&1 | \ + $SED -ne 's/^.*Version \(.*\)$/\1/p'`" + # We want the version as an integer so we can test against + # which version we're using. SWIG doesn't provide this + # to us so we have to come up with it on our own. + # The major is passed straight through, + # the minor is zero padded to two places, + # and the patch level is zero padded to three places. + # e.g. 1.3.24 becomes 103024 + SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \ + $SED -e 's/[^0-9\.].*$//' \ + -e 's/\.\([0-9]\)$/.0\1/' \ + -e 's/\.\([0-9][0-9]\)$/.0\1/' \ + -e 's/\.\([0-9]\)\./0\1/; s/\.//g;'`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_VERSION_RAW" >&5 +$as_echo "$SWIG_VERSION_RAW" >&6; } + # If you change the required swig version number, don't forget to update: + # subversion/bindings/swig/INSTALL + # packages/rpm/redhat-8+/subversion.spec + # packages/rpm/redhat-7.x/subversion.spec + # packages/rpm/rhel-3/subversion.spec + # packages/rpm/rhel-4/subversion.spec + if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then + SWIG_SUITABLE=yes + else + SWIG_SUITABLE=no + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&5 +$as_echo "$as_me: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion requires SWIG 1.3.24 or later" >&5 +$as_echo "$as_me: WARNING: Subversion requires SWIG 1.3.24 or later" >&2;} + fi + fi + + SWIG_PY_COMPILE="none" + SWIG_PY_LINK="none" + if test "$PYTHON" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring python swig binding" >&5 +$as_echo "$as_me: Configuring python swig binding" >&6;} + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python includes" >&5 +$as_echo_n "checking for Python includes... " >&6; } +if ${ac_cv_python_includes+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_includes" >&5 +$as_echo "$ac_cv_python_includes" >&6; } + SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes" + + if test "$ac_cv_python_includes" = "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python bindings cannot be built without distutils module" >&5 +$as_echo "$as_me: WARNING: python bindings cannot be built without distutils module" >&2;} + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiling Python extensions" >&5 +$as_echo_n "checking for compiling Python extensions... " >&6; } +if ${ac_cv_python_compile+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_compile" >&5 +$as_echo "$ac_cv_python_compile" >&6; } + SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python extensions" >&5 +$as_echo_n "checking for linking Python extensions... " >&6; } +if ${ac_cv_python_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_link" >&5 +$as_echo "$ac_cv_python_link" >&6; } + SWIG_PY_LINK="$ac_cv_python_link" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python libraries" >&5 +$as_echo_n "checking for linking Python libraries... " >&6; } +if ${ac_cv_python_libs+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_libs" >&5 +$as_echo "$ac_cv_python_libs" >&6; } + SWIG_PY_LIBS="` + input_flags="$ac_cv_python_libs" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + SVN_PYCFMT_SAVE_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_int64_t Python/C API format string" >&5 +$as_echo_n "checking for apr_int64_t Python/C API format string... " >&6; } +if ${svn_cv_pycfmt_apr_int64_t+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"lld\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="L" +fi +rm -f conftest* + + fi + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +r + #include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"ld\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="l" +fi +rm -f conftest* + + fi + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"d\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="i" +fi +rm -f conftest* + + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_pycfmt_apr_int64_t" >&5 +$as_echo "$svn_cv_pycfmt_apr_int64_t" >&6; } + CPPFLAGS="$SVN_PYCFMT_SAVE_CPPFLAGS" + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + as_fn_error $? "failed to recognize APR_INT64_T_FMT on this platform" "$LINENO" 5 + fi + +cat >>confdefs.h <<_ACEOF +#define SVN_APR_INT64_T_PYCFMT "$svn_cv_pycfmt_apr_int64_t" +_ACEOF + + fi + + if test "$PERL" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking perl version" >&5 +$as_echo_n "checking perl version... " >&6; } + PERL_VERSION="`$PERL -e 'q([); print $] * 1000000,$/;'`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL_VERSION" >&5 +$as_echo "$PERL_VERSION" >&6; } + if test "$PERL_VERSION" -ge "5008000"; then + SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: perl bindings require perl 5.8.0 or newer." >&5 +$as_echo "$as_me: WARNING: perl bindings require perl 5.8.0 or newer." >&2;} + fi + fi + + SWIG_RB_COMPILE="none" + SWIG_RB_LINK="none" + if test "$RUBY" != "none"; then + rbconfig="$RUBY -rrbconfig -e " + + for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \ + rubyhdrdir sitedir sitelibdir sitearchdir libdir + do + rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG['$var_name']"` + eval "rbconfig_$var_name=\"$rbconfig_tmp\"" + done + + { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ruby SWIG binding" >&5 +$as_echo "$as_me: Configuring Ruby SWIG binding" >&6;} + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby include path" >&5 +$as_echo_n "checking for Ruby include path... " >&6; } +if ${svn_cv_ruby_includes+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test -d "$rbconfig_rubyhdrdir"; then + svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir -I$rbconfig_rubyhdrdir/ruby -I$rbconfig_rubyhdrdir/ruby/backward -I$rbconfig_rubyhdrdir/$rbconfig_arch" + else + svn_cv_ruby_includes="-I. -I$rbconfig_archdir" + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_includes" >&5 +$as_echo "$svn_cv_ruby_includes" >&6; } + SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile Ruby extensions" >&5 +$as_echo_n "checking how to compile Ruby extensions... " >&6; } +if ${svn_cv_ruby_compile+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_compile="$rbconfig_CC $CFLAGS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_compile" >&5 +$as_echo "$svn_cv_ruby_compile" >&6; } + SWIG_RB_COMPILE="$svn_cv_ruby_compile" + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-ansi//'` + + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c89//'` + + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c90//'` + + SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby extensions" >&5 +$as_echo_n "checking how to link Ruby extensions... " >&6; } +if ${svn_cv_ruby_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \ + $rbconfig_LDSHARED`" + svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link" + svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_link" >&5 +$as_echo "$svn_cv_ruby_link" >&6; } + SWIG_RB_LINK="$svn_cv_ruby_link" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby libraries" >&5 +$as_echo_n "checking how to link Ruby libraries... " >&6; } +if ${ac_cv_ruby_libs+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ruby_libs" >&5 +$as_echo "$ac_cv_ruby_libs" >&6; } + SWIG_RB_LIBS="` + input_flags="$ac_cv_ruby_libs" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rb_errinfo" >&5 +$as_echo_n "checking for rb_errinfo... " >&6; } + old_CFLAGS="$CFLAGS" + old_LIBS="$LIBS" + CFLAGS="$CFLAGS $svn_cv_ruby_includes" + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-ansi//'` + + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c89//'` + + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c90//'` + + LIBS="$SWIG_RB_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +int main() +{rb_errinfo();} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + have_rb_errinfo="yes" +else + have_rb_errinfo="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test "$have_rb_errinfo" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define HAVE_RB_ERRINFO 1" >>confdefs.h + + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + CFLAGS="$old_CFLAGS" + LIBS="$old_LIBS" + + if ${svn_cv_ruby_sitedir+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir="$rbconfig_sitedir" + +fi + + +# Check whether --with-ruby-sitedir was given. +if test "${with_ruby_sitedir+set}" = set; then : + withval=$with_ruby_sitedir; svn_ruby_installdir="$withval" +else + svn_ruby_installdir="$svn_cv_ruby_sitedir" +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby scripts" >&5 +$as_echo_n "checking where to install Ruby scripts... " >&6; } + if ${svn_cv_ruby_sitedir_libsuffix+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + +fi + + SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_LIB_DIR" >&5 +$as_echo "$SWIG_RB_SITE_LIB_DIR" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby extensions" >&5 +$as_echo_n "checking where to install Ruby extensions... " >&6; } + if ${svn_cv_ruby_sitedir_archsuffix+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + +fi + + SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_ARCH_DIR" >&5 +$as_echo "$SWIG_RB_SITE_ARCH_DIR" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to use output level for Ruby bindings tests" >&5 +$as_echo_n "checking how to use output level for Ruby bindings tests... " >&6; } + if ${svn_cv_ruby_test_verbose+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_test_verbose="normal" + +fi + + +# Check whether --with-ruby-test-verbose was given. +if test "${with_ruby_test_verbose+set}" = set; then : + withval=$with_ruby_test_verbose; svn_ruby_test_verbose="$withval" +else + svn_ruby_test_verbose="$svn_cv_ruby_test_verbose" +fi + + SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_TEST_VERBOSE" >&5 +$as_echo "$SWIG_RB_TEST_VERBOSE" >&6; } + fi + + + + + + + + + + + + + + + ;; + *) + + where=$withval + + if test $where = no; then + SWIG=none + elif test $where = check; then + # Extract the first word of "swig", so it can be a program name with args. +set dummy swig; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_SWIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $SWIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_SWIG="$SWIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_SWIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_SWIG" && ac_cv_path_SWIG="none" + ;; +esac +fi +SWIG=$ac_cv_path_SWIG +if test -n "$SWIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG" >&5 +$as_echo "$SWIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + if test -f "$where"; then + SWIG="$where" + else + SWIG="$where/bin/swig" + fi + if test ! -f "$SWIG" || test ! -x "$SWIG"; then + as_fn_error $? "Could not find swig binary at $SWIG" "$LINENO" 5 + fi + fi + + if test "$SWIG" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking swig version" >&5 +$as_echo_n "checking swig version... " >&6; } + SWIG_VERSION_RAW="`$SWIG -version 2>&1 | \ + $SED -ne 's/^.*Version \(.*\)$/\1/p'`" + # We want the version as an integer so we can test against + # which version we're using. SWIG doesn't provide this + # to us so we have to come up with it on our own. + # The major is passed straight through, + # the minor is zero padded to two places, + # and the patch level is zero padded to three places. + # e.g. 1.3.24 becomes 103024 + SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \ + $SED -e 's/[^0-9\.].*$//' \ + -e 's/\.\([0-9]\)$/.0\1/' \ + -e 's/\.\([0-9][0-9]\)$/.0\1/' \ + -e 's/\.\([0-9]\)\./0\1/; s/\.//g;'`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_VERSION_RAW" >&5 +$as_echo "$SWIG_VERSION_RAW" >&6; } + # If you change the required swig version number, don't forget to update: + # subversion/bindings/swig/INSTALL + # packages/rpm/redhat-8+/subversion.spec + # packages/rpm/redhat-7.x/subversion.spec + # packages/rpm/rhel-3/subversion.spec + # packages/rpm/rhel-4/subversion.spec + if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then + SWIG_SUITABLE=yes + else + SWIG_SUITABLE=no + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&5 +$as_echo "$as_me: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion requires SWIG 1.3.24 or later" >&5 +$as_echo "$as_me: WARNING: Subversion requires SWIG 1.3.24 or later" >&2;} + fi + fi + + SWIG_PY_COMPILE="none" + SWIG_PY_LINK="none" + if test "$PYTHON" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring python swig binding" >&5 +$as_echo "$as_me: Configuring python swig binding" >&6;} + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python includes" >&5 +$as_echo_n "checking for Python includes... " >&6; } +if ${ac_cv_python_includes+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_includes" >&5 +$as_echo "$ac_cv_python_includes" >&6; } + SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes" + + if test "$ac_cv_python_includes" = "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python bindings cannot be built without distutils module" >&5 +$as_echo "$as_me: WARNING: python bindings cannot be built without distutils module" >&2;} + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiling Python extensions" >&5 +$as_echo_n "checking for compiling Python extensions... " >&6; } +if ${ac_cv_python_compile+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_compile" >&5 +$as_echo "$ac_cv_python_compile" >&6; } + SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python extensions" >&5 +$as_echo_n "checking for linking Python extensions... " >&6; } +if ${ac_cv_python_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_link" >&5 +$as_echo "$ac_cv_python_link" >&6; } + SWIG_PY_LINK="$ac_cv_python_link" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python libraries" >&5 +$as_echo_n "checking for linking Python libraries... " >&6; } +if ${ac_cv_python_libs+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_libs" >&5 +$as_echo "$ac_cv_python_libs" >&6; } + SWIG_PY_LIBS="` + input_flags="$ac_cv_python_libs" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + SVN_PYCFMT_SAVE_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_int64_t Python/C API format string" >&5 +$as_echo_n "checking for apr_int64_t Python/C API format string... " >&6; } +if ${svn_cv_pycfmt_apr_int64_t+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"lld\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="L" +fi +rm -f conftest* + + fi + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +r + #include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"ld\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="l" +fi +rm -f conftest* + + fi + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"d\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="i" +fi +rm -f conftest* + + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_pycfmt_apr_int64_t" >&5 +$as_echo "$svn_cv_pycfmt_apr_int64_t" >&6; } + CPPFLAGS="$SVN_PYCFMT_SAVE_CPPFLAGS" + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + as_fn_error $? "failed to recognize APR_INT64_T_FMT on this platform" "$LINENO" 5 + fi + +cat >>confdefs.h <<_ACEOF +#define SVN_APR_INT64_T_PYCFMT "$svn_cv_pycfmt_apr_int64_t" +_ACEOF + + fi + + if test "$PERL" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking perl version" >&5 +$as_echo_n "checking perl version... " >&6; } + PERL_VERSION="`$PERL -e 'q([); print $] * 1000000,$/;'`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL_VERSION" >&5 +$as_echo "$PERL_VERSION" >&6; } + if test "$PERL_VERSION" -ge "5008000"; then + SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: perl bindings require perl 5.8.0 or newer." >&5 +$as_echo "$as_me: WARNING: perl bindings require perl 5.8.0 or newer." >&2;} + fi + fi + + SWIG_RB_COMPILE="none" + SWIG_RB_LINK="none" + if test "$RUBY" != "none"; then + rbconfig="$RUBY -rrbconfig -e " + + for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \ + rubyhdrdir sitedir sitelibdir sitearchdir libdir + do + rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG['$var_name']"` + eval "rbconfig_$var_name=\"$rbconfig_tmp\"" + done + + { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ruby SWIG binding" >&5 +$as_echo "$as_me: Configuring Ruby SWIG binding" >&6;} + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby include path" >&5 +$as_echo_n "checking for Ruby include path... " >&6; } +if ${svn_cv_ruby_includes+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test -d "$rbconfig_rubyhdrdir"; then + svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir -I$rbconfig_rubyhdrdir/ruby -I$rbconfig_rubyhdrdir/ruby/backward -I$rbconfig_rubyhdrdir/$rbconfig_arch" + else + svn_cv_ruby_includes="-I. -I$rbconfig_archdir" + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_includes" >&5 +$as_echo "$svn_cv_ruby_includes" >&6; } + SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile Ruby extensions" >&5 +$as_echo_n "checking how to compile Ruby extensions... " >&6; } +if ${svn_cv_ruby_compile+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_compile="$rbconfig_CC $CFLAGS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_compile" >&5 +$as_echo "$svn_cv_ruby_compile" >&6; } + SWIG_RB_COMPILE="$svn_cv_ruby_compile" + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-ansi//'` + + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c89//'` + + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c90//'` + + SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby extensions" >&5 +$as_echo_n "checking how to link Ruby extensions... " >&6; } +if ${svn_cv_ruby_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \ + $rbconfig_LDSHARED`" + svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link" + svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_link" >&5 +$as_echo "$svn_cv_ruby_link" >&6; } + SWIG_RB_LINK="$svn_cv_ruby_link" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby libraries" >&5 +$as_echo_n "checking how to link Ruby libraries... " >&6; } +if ${ac_cv_ruby_libs+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ruby_libs" >&5 +$as_echo "$ac_cv_ruby_libs" >&6; } + SWIG_RB_LIBS="` + input_flags="$ac_cv_ruby_libs" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rb_errinfo" >&5 +$as_echo_n "checking for rb_errinfo... " >&6; } + old_CFLAGS="$CFLAGS" + old_LIBS="$LIBS" + CFLAGS="$CFLAGS $svn_cv_ruby_includes" + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-ansi//'` + + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c89//'` + + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c90//'` + + LIBS="$SWIG_RB_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +int main() +{rb_errinfo();} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + have_rb_errinfo="yes" +else + have_rb_errinfo="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test "$have_rb_errinfo" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define HAVE_RB_ERRINFO 1" >>confdefs.h + + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + CFLAGS="$old_CFLAGS" + LIBS="$old_LIBS" + + if ${svn_cv_ruby_sitedir+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir="$rbconfig_sitedir" + +fi + + +# Check whether --with-ruby-sitedir was given. +if test "${with_ruby_sitedir+set}" = set; then : + withval=$with_ruby_sitedir; svn_ruby_installdir="$withval" +else + svn_ruby_installdir="$svn_cv_ruby_sitedir" +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby scripts" >&5 +$as_echo_n "checking where to install Ruby scripts... " >&6; } + if ${svn_cv_ruby_sitedir_libsuffix+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + +fi + + SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_LIB_DIR" >&5 +$as_echo "$SWIG_RB_SITE_LIB_DIR" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby extensions" >&5 +$as_echo_n "checking where to install Ruby extensions... " >&6; } + if ${svn_cv_ruby_sitedir_archsuffix+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + +fi + + SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_ARCH_DIR" >&5 +$as_echo "$SWIG_RB_SITE_ARCH_DIR" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to use output level for Ruby bindings tests" >&5 +$as_echo_n "checking how to use output level for Ruby bindings tests... " >&6; } + if ${svn_cv_ruby_test_verbose+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_test_verbose="normal" + +fi + + +# Check whether --with-ruby-test-verbose was given. +if test "${with_ruby_test_verbose+set}" = set; then : + withval=$with_ruby_test_verbose; svn_ruby_test_verbose="$withval" +else + svn_ruby_test_verbose="$svn_cv_ruby_test_verbose" +fi + + SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_TEST_VERBOSE" >&5 +$as_echo "$SWIG_RB_TEST_VERBOSE" >&6; } + fi + + + + + + + + + + + + + + + ;; + esac + +else + + + where=check + + if test $where = no; then + SWIG=none + elif test $where = check; then + # Extract the first word of "swig", so it can be a program name with args. +set dummy swig; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_SWIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $SWIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_SWIG="$SWIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_SWIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_SWIG" && ac_cv_path_SWIG="none" + ;; +esac +fi +SWIG=$ac_cv_path_SWIG +if test -n "$SWIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG" >&5 +$as_echo "$SWIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + if test -f "$where"; then + SWIG="$where" + else + SWIG="$where/bin/swig" + fi + if test ! -f "$SWIG" || test ! -x "$SWIG"; then + as_fn_error $? "Could not find swig binary at $SWIG" "$LINENO" 5 + fi + fi + + if test "$SWIG" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking swig version" >&5 +$as_echo_n "checking swig version... " >&6; } + SWIG_VERSION_RAW="`$SWIG -version 2>&1 | \ + $SED -ne 's/^.*Version \(.*\)$/\1/p'`" + # We want the version as an integer so we can test against + # which version we're using. SWIG doesn't provide this + # to us so we have to come up with it on our own. + # The major is passed straight through, + # the minor is zero padded to two places, + # and the patch level is zero padded to three places. + # e.g. 1.3.24 becomes 103024 + SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \ + $SED -e 's/[^0-9\.].*$//' \ + -e 's/\.\([0-9]\)$/.0\1/' \ + -e 's/\.\([0-9][0-9]\)$/.0\1/' \ + -e 's/\.\([0-9]\)\./0\1/; s/\.//g;'`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_VERSION_RAW" >&5 +$as_echo "$SWIG_VERSION_RAW" >&6; } + # If you change the required swig version number, don't forget to update: + # subversion/bindings/swig/INSTALL + # packages/rpm/redhat-8+/subversion.spec + # packages/rpm/redhat-7.x/subversion.spec + # packages/rpm/rhel-3/subversion.spec + # packages/rpm/rhel-4/subversion.spec + if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then + SWIG_SUITABLE=yes + else + SWIG_SUITABLE=no + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&5 +$as_echo "$as_me: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion requires SWIG 1.3.24 or later" >&5 +$as_echo "$as_me: WARNING: Subversion requires SWIG 1.3.24 or later" >&2;} + fi + fi + + SWIG_PY_COMPILE="none" + SWIG_PY_LINK="none" + if test "$PYTHON" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring python swig binding" >&5 +$as_echo "$as_me: Configuring python swig binding" >&6;} + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python includes" >&5 +$as_echo_n "checking for Python includes... " >&6; } +if ${ac_cv_python_includes+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_includes" >&5 +$as_echo "$ac_cv_python_includes" >&6; } + SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes" + + if test "$ac_cv_python_includes" = "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python bindings cannot be built without distutils module" >&5 +$as_echo "$as_me: WARNING: python bindings cannot be built without distutils module" >&2;} + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiling Python extensions" >&5 +$as_echo_n "checking for compiling Python extensions... " >&6; } +if ${ac_cv_python_compile+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_compile" >&5 +$as_echo "$ac_cv_python_compile" >&6; } + SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python extensions" >&5 +$as_echo_n "checking for linking Python extensions... " >&6; } +if ${ac_cv_python_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_link" >&5 +$as_echo "$ac_cv_python_link" >&6; } + SWIG_PY_LINK="$ac_cv_python_link" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python libraries" >&5 +$as_echo_n "checking for linking Python libraries... " >&6; } +if ${ac_cv_python_libs+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_libs" >&5 +$as_echo "$ac_cv_python_libs" >&6; } + SWIG_PY_LIBS="` + input_flags="$ac_cv_python_libs" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + SVN_PYCFMT_SAVE_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_int64_t Python/C API format string" >&5 +$as_echo_n "checking for apr_int64_t Python/C API format string... " >&6; } +if ${svn_cv_pycfmt_apr_int64_t+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"lld\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="L" +fi +rm -f conftest* + + fi + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +r + #include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"ld\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="l" +fi +rm -f conftest* + + fi + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + MaTcHtHiS APR_INT64_T_FMT EnDeNd +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "MaTcHtHiS +\"d\" +EnDeNd" >/dev/null 2>&1; then : + svn_cv_pycfmt_apr_int64_t="i" +fi +rm -f conftest* + + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_pycfmt_apr_int64_t" >&5 +$as_echo "$svn_cv_pycfmt_apr_int64_t" >&6; } + CPPFLAGS="$SVN_PYCFMT_SAVE_CPPFLAGS" + if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then + as_fn_error $? "failed to recognize APR_INT64_T_FMT on this platform" "$LINENO" 5 + fi + +cat >>confdefs.h <<_ACEOF +#define SVN_APR_INT64_T_PYCFMT "$svn_cv_pycfmt_apr_int64_t" +_ACEOF + + fi + + if test "$PERL" != "none"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking perl version" >&5 +$as_echo_n "checking perl version... " >&6; } + PERL_VERSION="`$PERL -e 'q([); print $] * 1000000,$/;'`" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL_VERSION" >&5 +$as_echo "$PERL_VERSION" >&6; } + if test "$PERL_VERSION" -ge "5008000"; then + SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: perl bindings require perl 5.8.0 or newer." >&5 +$as_echo "$as_me: WARNING: perl bindings require perl 5.8.0 or newer." >&2;} + fi + fi + + SWIG_RB_COMPILE="none" + SWIG_RB_LINK="none" + if test "$RUBY" != "none"; then + rbconfig="$RUBY -rrbconfig -e " + + for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \ + rubyhdrdir sitedir sitelibdir sitearchdir libdir + do + rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG['$var_name']"` + eval "rbconfig_$var_name=\"$rbconfig_tmp\"" + done + + { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ruby SWIG binding" >&5 +$as_echo "$as_me: Configuring Ruby SWIG binding" >&6;} + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby include path" >&5 +$as_echo_n "checking for Ruby include path... " >&6; } +if ${svn_cv_ruby_includes+:} false; then : + $as_echo_n "(cached) " >&6 +else + + if test -d "$rbconfig_rubyhdrdir"; then + svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir -I$rbconfig_rubyhdrdir/ruby -I$rbconfig_rubyhdrdir/ruby/backward -I$rbconfig_rubyhdrdir/$rbconfig_arch" + else + svn_cv_ruby_includes="-I. -I$rbconfig_archdir" + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_includes" >&5 +$as_echo "$svn_cv_ruby_includes" >&6; } + SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile Ruby extensions" >&5 +$as_echo_n "checking how to compile Ruby extensions... " >&6; } +if ${svn_cv_ruby_compile+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_compile="$rbconfig_CC $CFLAGS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_compile" >&5 +$as_echo "$svn_cv_ruby_compile" >&6; } + SWIG_RB_COMPILE="$svn_cv_ruby_compile" + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-ansi//'` + + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c89//'` + + + SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c90//'` + + SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby extensions" >&5 +$as_echo_n "checking how to link Ruby extensions... " >&6; } +if ${svn_cv_ruby_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \ + $rbconfig_LDSHARED`" + svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link" + svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_link" >&5 +$as_echo "$svn_cv_ruby_link" >&6; } + SWIG_RB_LINK="$svn_cv_ruby_link" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby libraries" >&5 +$as_echo_n "checking how to link Ruby libraries... " >&6; } +if ${ac_cv_ruby_libs+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ruby_libs" >&5 +$as_echo "$ac_cv_ruby_libs" >&6; } + SWIG_RB_LIBS="` + input_flags="$ac_cv_ruby_libs" + output_flags="" + filtered_dirs="/lib /lib64 /usr/lib /usr/lib64" + for flag in $input_flags; do + filter="no" + for dir in $filtered_dirs; do + if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then + filter="yes" + break + fi + done + if test "$filter" = "no"; then + output_flags="$output_flags $flag" + fi + done + if test -n "$output_flags"; then + printf "%s" "${output_flags# }" + fi +`" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rb_errinfo" >&5 +$as_echo_n "checking for rb_errinfo... " >&6; } + old_CFLAGS="$CFLAGS" + old_LIBS="$LIBS" + CFLAGS="$CFLAGS $svn_cv_ruby_includes" + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-ansi//'` + + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c89//'` + + + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c90//'` + + LIBS="$SWIG_RB_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +int main() +{rb_errinfo();} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + have_rb_errinfo="yes" +else + have_rb_errinfo="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test "$have_rb_errinfo" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define HAVE_RB_ERRINFO 1" >>confdefs.h + + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + CFLAGS="$old_CFLAGS" + LIBS="$old_LIBS" + + if ${svn_cv_ruby_sitedir+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir="$rbconfig_sitedir" + +fi + + +# Check whether --with-ruby-sitedir was given. +if test "${with_ruby_sitedir+set}" = set; then : + withval=$with_ruby_sitedir; svn_ruby_installdir="$withval" +else + svn_ruby_installdir="$svn_cv_ruby_sitedir" +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby scripts" >&5 +$as_echo_n "checking where to install Ruby scripts... " >&6; } + if ${svn_cv_ruby_sitedir_libsuffix+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + +fi + + SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_LIB_DIR" >&5 +$as_echo "$SWIG_RB_SITE_LIB_DIR" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby extensions" >&5 +$as_echo_n "checking where to install Ruby extensions... " >&6; } + if ${svn_cv_ruby_sitedir_archsuffix+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + +fi + + SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_ARCH_DIR" >&5 +$as_echo "$SWIG_RB_SITE_ARCH_DIR" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to use output level for Ruby bindings tests" >&5 +$as_echo_n "checking how to use output level for Ruby bindings tests... " >&6; } + if ${svn_cv_ruby_test_verbose+:} false; then : + $as_echo_n "(cached) " >&6 +else + + svn_cv_ruby_test_verbose="normal" + +fi + + +# Check whether --with-ruby-test-verbose was given. +if test "${with_ruby_test_verbose+set}" = set; then : + withval=$with_ruby_test_verbose; svn_ruby_test_verbose="$withval" +else + svn_ruby_test_verbose="$svn_cv_ruby_test_verbose" +fi + + SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_TEST_VERBOSE" >&5 +$as_echo "$SWIG_RB_TEST_VERBOSE" >&6; } + fi + + + + + + + + + + + + + + + +fi + + + + + +# Check whether --with-ctypesgen was given. +if test "${with_ctypesgen+set}" = set; then : + withval=$with_ctypesgen; + case "$withval" in + "no") + + where=no + + CTYPESGEN=none + + if test $where = check; then + # Extract the first word of ""ctypesgen.py"", so it can be a program name with args. +set dummy "ctypesgen.py"; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_CTYPESGEN+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $CTYPESGEN in + [\\/]* | ?:[\\/]*) + ac_cv_path_CTYPESGEN="$CTYPESGEN" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_CTYPESGEN="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_CTYPESGEN" && ac_cv_path_CTYPESGEN="none" + ;; +esac +fi +CTYPESGEN=$ac_cv_path_CTYPESGEN +if test -n "$CTYPESGEN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5 +$as_echo "$CTYPESGEN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + elif test $where != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ctypesgen.py" >&5 +$as_echo_n "checking for ctypesgen.py... " >&6; } + + if test -f "$where"; then + CTYPESGEN="$where" + elif test -f "$where/bin/ctypesgen.py"; then + CTYPESGEN="$where/bin/ctypesgen.py" + else + CTYPESGEN="$where/ctypesgen.py" + fi + + if test ! -f "$CTYPESGEN" || test ! -x "$CTYPESGEN"; then + as_fn_error $? "Could not find ctypesgen at $where/ctypesgen.py or at + $where/bin/ctypesgen.py" "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5 +$as_echo "$CTYPESGEN" >&6; } + fi + fi + + + + ;; + "yes") + + where=check + + CTYPESGEN=none + + if test $where = check; then + # Extract the first word of ""ctypesgen.py"", so it can be a program name with args. +set dummy "ctypesgen.py"; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_CTYPESGEN+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $CTYPESGEN in + [\\/]* | ?:[\\/]*) + ac_cv_path_CTYPESGEN="$CTYPESGEN" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_CTYPESGEN="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_CTYPESGEN" && ac_cv_path_CTYPESGEN="none" + ;; +esac +fi +CTYPESGEN=$ac_cv_path_CTYPESGEN +if test -n "$CTYPESGEN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5 +$as_echo "$CTYPESGEN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + elif test $where != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ctypesgen.py" >&5 +$as_echo_n "checking for ctypesgen.py... " >&6; } + + if test -f "$where"; then + CTYPESGEN="$where" + elif test -f "$where/bin/ctypesgen.py"; then + CTYPESGEN="$where/bin/ctypesgen.py" + else + CTYPESGEN="$where/ctypesgen.py" + fi + + if test ! -f "$CTYPESGEN" || test ! -x "$CTYPESGEN"; then + as_fn_error $? "Could not find ctypesgen at $where/ctypesgen.py or at + $where/bin/ctypesgen.py" "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5 +$as_echo "$CTYPESGEN" >&6; } + fi + fi + + + + ;; + *) + + where=$withval + + CTYPESGEN=none + + if test $where = check; then + # Extract the first word of ""ctypesgen.py"", so it can be a program name with args. +set dummy "ctypesgen.py"; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_CTYPESGEN+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $CTYPESGEN in + [\\/]* | ?:[\\/]*) + ac_cv_path_CTYPESGEN="$CTYPESGEN" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_CTYPESGEN="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_CTYPESGEN" && ac_cv_path_CTYPESGEN="none" + ;; +esac +fi +CTYPESGEN=$ac_cv_path_CTYPESGEN +if test -n "$CTYPESGEN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5 +$as_echo "$CTYPESGEN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + elif test $where != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ctypesgen.py" >&5 +$as_echo_n "checking for ctypesgen.py... " >&6; } + + if test -f "$where"; then + CTYPESGEN="$where" + elif test -f "$where/bin/ctypesgen.py"; then + CTYPESGEN="$where/bin/ctypesgen.py" + else + CTYPESGEN="$where/ctypesgen.py" + fi + + if test ! -f "$CTYPESGEN" || test ! -x "$CTYPESGEN"; then + as_fn_error $? "Could not find ctypesgen at $where/ctypesgen.py or at + $where/bin/ctypesgen.py" "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5 +$as_echo "$CTYPESGEN" >&6; } + fi + fi + + + + ;; + esac + +else + + + where=check + + CTYPESGEN=none + + if test $where = check; then + # Extract the first word of ""ctypesgen.py"", so it can be a program name with args. +set dummy "ctypesgen.py"; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_CTYPESGEN+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $CTYPESGEN in + [\\/]* | ?:[\\/]*) + ac_cv_path_CTYPESGEN="$CTYPESGEN" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_CTYPESGEN="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_CTYPESGEN" && ac_cv_path_CTYPESGEN="none" + ;; +esac +fi +CTYPESGEN=$ac_cv_path_CTYPESGEN +if test -n "$CTYPESGEN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5 +$as_echo "$CTYPESGEN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + elif test $where != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ctypesgen.py" >&5 +$as_echo_n "checking for ctypesgen.py... " >&6; } + + if test -f "$where"; then + CTYPESGEN="$where" + elif test -f "$where/bin/ctypesgen.py"; then + CTYPESGEN="$where/bin/ctypesgen.py" + else + CTYPESGEN="$where/ctypesgen.py" + fi + + if test ! -f "$CTYPESGEN" || test ! -x "$CTYPESGEN"; then + as_fn_error $? "Could not find ctypesgen at $where/ctypesgen.py or at + $where/bin/ctypesgen.py" "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5 +$as_echo "$CTYPESGEN" >&6; } + fi + fi + + + + +fi + + + +# Check whether --enable-runtime-module-search was given. +if test "${enable_runtime_module_search+set}" = set; then : + enableval=$enable_runtime_module_search; + if test "$enableval" = "yes"; then + use_dso=yes + if test "$svn_enable_shared" = "no"; then + as_fn_error $? "--enable-runtime-module-search conflicts with --disable-shared" "$LINENO" 5 + fi + +$as_echo "#define SVN_USE_DSO 1" >>confdefs.h + + fi + +fi + + +if test "$svn_enable_shared" = "no" || test "$use_dso" != "yes"; then + +$as_echo "#define SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL 1" >>confdefs.h + + svn_ra_lib_deps="\$(RA_LOCAL_DEPS)" + svn_ra_lib_install_deps="install-ramod-lib" + svn_ra_lib_link="\$(RA_LOCAL_LINK)" + + +$as_echo "#define SVN_LIBSVN_CLIENT_LINKS_RA_SVN 1" >>confdefs.h + + svn_ra_lib_deps="$svn_ra_lib_deps \$(RA_SVN_DEPS)" + svn_ra_lib_link="$svn_ra_lib_link \$(RA_SVN_LINK)" + + if test "$svn_lib_serf" = "yes"; then + +$as_echo "#define SVN_LIBSVN_CLIENT_LINKS_RA_SERF 1" >>confdefs.h + + svn_ra_lib_deps="$svn_ra_lib_deps \$(RA_SERF_DEPS)" + svn_ra_lib_install_deps="$svn_ra_lib_install_deps install-serf-lib" + svn_ra_lib_link="$svn_ra_lib_link \$(RA_SERF_LINK)" + fi + + SVN_RA_LIB_DEPS=$svn_ra_lib_deps + SVN_RA_LIB_INSTALL_DEPS=$svn_ra_lib_install_deps + SVN_RA_LIB_LINK=$svn_ra_lib_link + + +$as_echo "#define SVN_LIBSVN_FS_LINKS_FS_FS 1" >>confdefs.h + + svn_fs_lib_deps="\$(FS_FS_DEPS)" + svn_fs_lib_install_deps="install-fsmod-lib" + svn_fs_lib_link="\$(FS_FS_LINK)" + + if test "$svn_lib_berkeley_db" = "yes"; then + +$as_echo "#define SVN_LIBSVN_FS_LINKS_FS_BASE 1" >>confdefs.h + + svn_fs_lib_deps="$svn_fs_lib_deps \$(FS_BASE_DEPS)" + svn_fs_lib_install_deps="$svn_fs_lib_install_deps install-bdb-lib" + svn_fs_lib_link="$svn_fs_lib_link \$(FS_BASE_LINK)" + fi + + SVN_FS_LIB_DEPS=$svn_fs_lib_deps + SVN_FS_LIB_INSTALL_DEPS=$svn_fs_lib_install_deps + SVN_FS_LIB_LINK=$svn_fs_lib_link +fi + + + + + + + + +# ==== JavaHL ================================================================ + +do_javahl_build=no +# Check whether --enable-javahl was given. +if test "${enable_javahl+set}" = set; then : + enableval=$enable_javahl; if test "$enableval" = "yes" ; then + do_javahl_build="yes" + fi + +fi + + +JAVAHL_OBJDIR="" +INSTALL_EXTRA_JAVAHL_LIB="" +FIX_JAVAHL_LIB="" +JAVAHL_TESTS_TARGET="" +JAVAHL_COMPAT_TESTS_TARGET="" +LT_CXX_LIBADD="" +if test "$do_javahl_build" = "yes"; then + if test "$JDK_SUITABLE" = "no"; then + as_fn_error $? "Cannot compile JavaHL without a suitable JDK. + Please specify a suitable JDK using the --with-jdk option." "$LINENO" 5 + fi + + JAVAHL_OBJDIR='$(libsvnjavahl_PATH)/.libs' + + os_arch=`uname` + if test "$os_arch" = "Darwin"; then + INSTALL_EXTRA_JAVAHL_LIB='ln -sf $(libdir)/libsvnjavahl-1.dylib $(libdir)/libsvnjavahl-1.jnilib' + FIX_JAVAHL_LIB="ln -sf libsvnjavahl-1.dylib $JAVAHL_OBJDIR/libsvnjavahl-1.jnilib" + fi + # This segment (and the rest of r10800) is very likely unnecessary + # with libtool 1.5, which automatically adds libstdc++ as a + # dependency to the C++ libraries it builds. So at some future time + # when autogen.sh requires libtool 1.5 or higher, we can get rid of + # it. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for additional flags to link C++ libraries" >&5 +$as_echo_n "checking for additional flags to link C++ libraries... " >&6; } + if test "x$ac_compiler_gnu" = "xyes"; then + LT_CXX_LIBADD="-lstdc++" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LT_CXX_LIBADD" >&5 +$as_echo "$LT_CXX_LIBADD" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } + fi +fi + + + + + + +# Check whether --with-junit was given. +if test "${with_junit+set}" = set; then : + withval=$with_junit; + if test "$withval" != "no"; then + if test -n "$JAVA_CLASSPATH"; then + JAVA_CLASSPATH="$withval:$JAVA_CLASSPATH" + else + JAVA_CLASSPATH="$withval" + fi + JAVAHL_TESTS_TARGET="javahl-tests" + JAVAHL_COMPAT_TESTS_TARGET="javahl-compat-tests" + fi + +fi + + + + + +# ==== Miscellaneous bits ==================================================== + +# Strip '-no-cpp-precomp' from CPPFLAGS for the clang compiler +### I think we get this flag from APR, so the fix probably belongs there +if test "$CC" = "clang"; then + + CPPFLAGS=`echo "$CPPFLAGS" | $SED -e 's/-no-cpp-precomp //'` + +fi + + +cat >>confdefs.h <<_ACEOF +#define SVN_PATH_LOCAL_SEPARATOR '/' +_ACEOF + + + +cat >>confdefs.h <<_ACEOF +#define SVN_NULL_DEVICE_NAME "/dev/null" +_ACEOF + + +DEFAULT_FS_TYPE="fsfs" + +cat >>confdefs.h <<_ACEOF +#define DEFAULT_FS_TYPE "$DEFAULT_FS_TYPE" +_ACEOF + + +DEFAULT_HTTP_LIBRARY="serf" + +cat >>confdefs.h <<_ACEOF +#define DEFAULT_HTTP_LIBRARY "$DEFAULT_HTTP_LIBRARY" +_ACEOF + + +# BSD/OS (BSDi) needs to use a different include syntax in Makefile +INCLUDE_OUTPUTS="include \$(top_srcdir)/build-outputs.mk" +case "$host" in + *bsdi*) + # Check whether they've installed GNU make + if ! make --version > /dev/null 2>&1; then + # BSDi make + INCLUDE_OUTPUTS=".include \"\$(top_srcdir)/build-outputs.mk\"" + fi + ;; +esac + + +# ==== Detection complete - output and run config.status ===================== + +ac_config_headers="$ac_config_headers subversion/svn_private_config.h.tmp:subversion/svn_private_config.h.in" + +ac_config_commands="$ac_config_commands svn_private_config.h.tmp" + +ac_config_files="$ac_config_files Makefile" + + + + SVN_CONFIG_SCRIPT_FILES="$SVN_CONFIG_SCRIPT_FILES tools/backup/hot-backup.py" + ac_config_files="$ac_config_files tools/backup/hot-backup.py" + + + SVN_CONFIG_SCRIPT_FILES="$SVN_CONFIG_SCRIPT_FILES tools/hook-scripts/commit-access-control.pl" + ac_config_files="$ac_config_files tools/hook-scripts/commit-access-control.pl" + + + SVN_CONFIG_SCRIPT_FILES="$SVN_CONFIG_SCRIPT_FILES subversion/bindings/swig/perl/native/Makefile.PL" + ac_config_files="$ac_config_files subversion/bindings/swig/perl/native/Makefile.PL" + +if test -e packages/solaris/pkginfo.in; then + + SVN_CONFIG_SCRIPT_FILES="$SVN_CONFIG_SCRIPT_FILES packages/solaris/pkginfo" + ac_config_files="$ac_config_files packages/solaris/pkginfo" + +fi + + +# Ensure that SWIG is checked after reconfiguration. +rm -f .swig_checked + + +cat >>confdefs.h <<_ACEOF +#define SVN_BUILD_HOST "${host}" +_ACEOF + + + +cat >>confdefs.h <<_ACEOF +#define SVN_BUILD_TARGET "${target}" +_ACEOF + + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DEFS=-DHAVE_CONFIG_H + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by subversion $as_me 1.8.0, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + +case $ac_config_headers in *" +"*) set x $ac_config_headers; shift; ac_config_headers=$*;; +esac + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_headers="$ac_config_headers" +config_commands="$ac_config_commands" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + --header=FILE[:TEMPLATE] + instantiate the configuration header FILE + +Configuration files: +$config_files + +Configuration headers: +$config_headers + +Configuration commands: +$config_commands + +Report bugs to ." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +subversion config.status 1.8.0 +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +AWK='$AWK' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --header | --heade | --head | --hea ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append CONFIG_HEADERS " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h) + # Conflict between --help and --header + as_fn_error $? "ambiguous option: \`$1' +Try \`$0 --help' for more information.";; + --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# +# INIT-COMMANDS +# + + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' +macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' +enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' +enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' +pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' +enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' +SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`' +ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`' +PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`' +host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`' +host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`' +host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`' +build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`' +build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`' +build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`' +SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`' +Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`' +GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`' +EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`' +FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`' +LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`' +NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`' +LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`' +max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`' +ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`' +exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`' +lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`' +lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`' +lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`' +lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`' +lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`' +reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`' +reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`' +OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`' +deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`' +file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`' +file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`' +want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`' +DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`' +sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`' +AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`' +AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`' +archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`' +STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`' +RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`' +old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`' +old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`' +lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`' +CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`' +CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`' +compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`' +GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`' +nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`' +lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`' +objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`' +MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`' +lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`' +need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`' +MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`' +DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`' +NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`' +LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`' +OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`' +OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`' +libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`' +shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`' +extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`' +enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`' +export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`' +whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`' +compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`' +old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`' +archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`' +module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`' +module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`' +with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`' +allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`' +no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`' +hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`' +hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`' +hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`' +hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`' +hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`' +inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`' +link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`' +always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`' +export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`' +exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`' +include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`' +prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`' +postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`' +file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`' +variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`' +need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`' +need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`' +version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`' +runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`' +libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`' +library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`' +soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`' +install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`' +postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`' +postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`' +finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`' +hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`' +sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`' +sys_lib_dlsearch_path_spec='`$ECHO "$sys_lib_dlsearch_path_spec" | $SED "$delay_single_quote_subst"`' +hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`' +enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`' +old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`' +striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_dirs='`$ECHO "$compiler_lib_search_dirs" | $SED "$delay_single_quote_subst"`' +predep_objects='`$ECHO "$predep_objects" | $SED "$delay_single_quote_subst"`' +postdep_objects='`$ECHO "$postdep_objects" | $SED "$delay_single_quote_subst"`' +predeps='`$ECHO "$predeps" | $SED "$delay_single_quote_subst"`' +postdeps='`$ECHO "$postdeps" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_path='`$ECHO "$compiler_lib_search_path" | $SED "$delay_single_quote_subst"`' +LD_CXX='`$ECHO "$LD_CXX" | $SED "$delay_single_quote_subst"`' +reload_flag_CXX='`$ECHO "$reload_flag_CXX" | $SED "$delay_single_quote_subst"`' +reload_cmds_CXX='`$ECHO "$reload_cmds_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_cmds_CXX='`$ECHO "$old_archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' +compiler_CXX='`$ECHO "$compiler_CXX" | $SED "$delay_single_quote_subst"`' +GCC_CXX='`$ECHO "$GCC_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_no_builtin_flag_CXX='`$ECHO "$lt_prog_compiler_no_builtin_flag_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_pic_CXX='`$ECHO "$lt_prog_compiler_pic_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_wl_CXX='`$ECHO "$lt_prog_compiler_wl_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_static_CXX='`$ECHO "$lt_prog_compiler_static_CXX" | $SED "$delay_single_quote_subst"`' +lt_cv_prog_compiler_c_o_CXX='`$ECHO "$lt_cv_prog_compiler_c_o_CXX" | $SED "$delay_single_quote_subst"`' +archive_cmds_need_lc_CXX='`$ECHO "$archive_cmds_need_lc_CXX" | $SED "$delay_single_quote_subst"`' +enable_shared_with_static_runtimes_CXX='`$ECHO "$enable_shared_with_static_runtimes_CXX" | $SED "$delay_single_quote_subst"`' +export_dynamic_flag_spec_CXX='`$ECHO "$export_dynamic_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +whole_archive_flag_spec_CXX='`$ECHO "$whole_archive_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +compiler_needs_object_CXX='`$ECHO "$compiler_needs_object_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_from_new_cmds_CXX='`$ECHO "$old_archive_from_new_cmds_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_from_expsyms_cmds_CXX='`$ECHO "$old_archive_from_expsyms_cmds_CXX" | $SED "$delay_single_quote_subst"`' +archive_cmds_CXX='`$ECHO "$archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' +archive_expsym_cmds_CXX='`$ECHO "$archive_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' +module_cmds_CXX='`$ECHO "$module_cmds_CXX" | $SED "$delay_single_quote_subst"`' +module_expsym_cmds_CXX='`$ECHO "$module_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' +with_gnu_ld_CXX='`$ECHO "$with_gnu_ld_CXX" | $SED "$delay_single_quote_subst"`' +allow_undefined_flag_CXX='`$ECHO "$allow_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' +no_undefined_flag_CXX='`$ECHO "$no_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_flag_spec_CXX='`$ECHO "$hardcode_libdir_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_separator_CXX='`$ECHO "$hardcode_libdir_separator_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_direct_CXX='`$ECHO "$hardcode_direct_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_direct_absolute_CXX='`$ECHO "$hardcode_direct_absolute_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_minus_L_CXX='`$ECHO "$hardcode_minus_L_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_shlibpath_var_CXX='`$ECHO "$hardcode_shlibpath_var_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_automatic_CXX='`$ECHO "$hardcode_automatic_CXX" | $SED "$delay_single_quote_subst"`' +inherit_rpath_CXX='`$ECHO "$inherit_rpath_CXX" | $SED "$delay_single_quote_subst"`' +link_all_deplibs_CXX='`$ECHO "$link_all_deplibs_CXX" | $SED "$delay_single_quote_subst"`' +always_export_symbols_CXX='`$ECHO "$always_export_symbols_CXX" | $SED "$delay_single_quote_subst"`' +export_symbols_cmds_CXX='`$ECHO "$export_symbols_cmds_CXX" | $SED "$delay_single_quote_subst"`' +exclude_expsyms_CXX='`$ECHO "$exclude_expsyms_CXX" | $SED "$delay_single_quote_subst"`' +include_expsyms_CXX='`$ECHO "$include_expsyms_CXX" | $SED "$delay_single_quote_subst"`' +prelink_cmds_CXX='`$ECHO "$prelink_cmds_CXX" | $SED "$delay_single_quote_subst"`' +postlink_cmds_CXX='`$ECHO "$postlink_cmds_CXX" | $SED "$delay_single_quote_subst"`' +file_list_spec_CXX='`$ECHO "$file_list_spec_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_action_CXX='`$ECHO "$hardcode_action_CXX" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_dirs_CXX='`$ECHO "$compiler_lib_search_dirs_CXX" | $SED "$delay_single_quote_subst"`' +predep_objects_CXX='`$ECHO "$predep_objects_CXX" | $SED "$delay_single_quote_subst"`' +postdep_objects_CXX='`$ECHO "$postdep_objects_CXX" | $SED "$delay_single_quote_subst"`' +predeps_CXX='`$ECHO "$predeps_CXX" | $SED "$delay_single_quote_subst"`' +postdeps_CXX='`$ECHO "$postdeps_CXX" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_path_CXX='`$ECHO "$compiler_lib_search_path_CXX" | $SED "$delay_single_quote_subst"`' + +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in SHELL \ +ECHO \ +PATH_SEPARATOR \ +SED \ +GREP \ +EGREP \ +FGREP \ +LD \ +NM \ +LN_S \ +lt_SP2NL \ +lt_NL2SP \ +reload_flag \ +OBJDUMP \ +deplibs_check_method \ +file_magic_cmd \ +file_magic_glob \ +want_nocaseglob \ +DLLTOOL \ +sharedlib_from_linklib_cmd \ +AR \ +AR_FLAGS \ +archiver_list_spec \ +STRIP \ +RANLIB \ +CC \ +CFLAGS \ +compiler \ +lt_cv_sys_global_symbol_pipe \ +lt_cv_sys_global_symbol_to_cdecl \ +lt_cv_sys_global_symbol_to_c_name_address \ +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \ +nm_file_list_spec \ +lt_prog_compiler_no_builtin_flag \ +lt_prog_compiler_pic \ +lt_prog_compiler_wl \ +lt_prog_compiler_static \ +lt_cv_prog_compiler_c_o \ +need_locks \ +MANIFEST_TOOL \ +DSYMUTIL \ +NMEDIT \ +LIPO \ +OTOOL \ +OTOOL64 \ +shrext_cmds \ +export_dynamic_flag_spec \ +whole_archive_flag_spec \ +compiler_needs_object \ +with_gnu_ld \ +allow_undefined_flag \ +no_undefined_flag \ +hardcode_libdir_flag_spec \ +hardcode_libdir_separator \ +exclude_expsyms \ +include_expsyms \ +file_list_spec \ +variables_saved_for_relink \ +libname_spec \ +library_names_spec \ +soname_spec \ +install_override_mode \ +finish_eval \ +old_striplib \ +striplib \ +compiler_lib_search_dirs \ +predep_objects \ +postdep_objects \ +predeps \ +postdeps \ +compiler_lib_search_path \ +LD_CXX \ +reload_flag_CXX \ +compiler_CXX \ +lt_prog_compiler_no_builtin_flag_CXX \ +lt_prog_compiler_pic_CXX \ +lt_prog_compiler_wl_CXX \ +lt_prog_compiler_static_CXX \ +lt_cv_prog_compiler_c_o_CXX \ +export_dynamic_flag_spec_CXX \ +whole_archive_flag_spec_CXX \ +compiler_needs_object_CXX \ +with_gnu_ld_CXX \ +allow_undefined_flag_CXX \ +no_undefined_flag_CXX \ +hardcode_libdir_flag_spec_CXX \ +hardcode_libdir_separator_CXX \ +exclude_expsyms_CXX \ +include_expsyms_CXX \ +file_list_spec_CXX \ +compiler_lib_search_dirs_CXX \ +predep_objects_CXX \ +postdep_objects_CXX \ +predeps_CXX \ +postdeps_CXX \ +compiler_lib_search_path_CXX; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in reload_cmds \ +old_postinstall_cmds \ +old_postuninstall_cmds \ +old_archive_cmds \ +extract_expsyms_cmds \ +old_archive_from_new_cmds \ +old_archive_from_expsyms_cmds \ +archive_cmds \ +archive_expsym_cmds \ +module_cmds \ +module_expsym_cmds \ +export_symbols_cmds \ +prelink_cmds \ +postlink_cmds \ +postinstall_cmds \ +postuninstall_cmds \ +finish_cmds \ +sys_lib_search_path_spec \ +sys_lib_dlsearch_path_spec \ +reload_cmds_CXX \ +old_archive_cmds_CXX \ +old_archive_from_new_cmds_CXX \ +old_archive_from_expsyms_cmds_CXX \ +archive_cmds_CXX \ +archive_expsym_cmds_CXX \ +module_cmds_CXX \ +module_expsym_cmds_CXX \ +export_symbols_cmds_CXX \ +prelink_cmds_CXX \ +postlink_cmds_CXX; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +ac_aux_dir='$ac_aux_dir' +xsi_shell='$xsi_shell' +lt_shell_append='$lt_shell_append' + +# See if we are running on zsh, and set the options which allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST +fi + + + PACKAGE='$PACKAGE' + VERSION='$VERSION' + TIMESTAMP='$TIMESTAMP' + RM='$RM' + ofile='$ofile' + + + + + +SED="$SED" + SVN_DB_HEADER="$SVN_DB_HEADER" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; + "subversion/svn_private_config.h.tmp") CONFIG_HEADERS="$CONFIG_HEADERS subversion/svn_private_config.h.tmp:subversion/svn_private_config.h.in" ;; + "svn_private_config.h.tmp") CONFIG_COMMANDS="$CONFIG_COMMANDS svn_private_config.h.tmp" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "tools/backup/hot-backup.py") CONFIG_FILES="$CONFIG_FILES tools/backup/hot-backup.py" ;; + "tools/hook-scripts/commit-access-control.pl") CONFIG_FILES="$CONFIG_FILES tools/hook-scripts/commit-access-control.pl" ;; + "subversion/bindings/swig/perl/native/Makefile.PL") CONFIG_FILES="$CONFIG_FILES subversion/bindings/swig/perl/native/Makefile.PL" ;; + "packages/solaris/pkginfo") CONFIG_FILES="$CONFIG_FILES packages/solaris/pkginfo" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + +# Set up the scripts for CONFIG_HEADERS section. +# No need to generate them if there are no CONFIG_HEADERS. +# This happens for instance with `./config.status Makefile'. +if test -n "$CONFIG_HEADERS"; then +cat >"$ac_tmp/defines.awk" <<\_ACAWK || +BEGIN { +_ACEOF + +# Transform confdefs.h into an awk script `defines.awk', embedded as +# here-document in config.status, that substitutes the proper values into +# config.h.in to produce config.h. + +# Create a delimiter string that does not exist in confdefs.h, to ease +# handling of long lines. +ac_delim='%!_!# ' +for ac_last_try in false false :; do + ac_tt=`sed -n "/$ac_delim/p" confdefs.h` + if test -z "$ac_tt"; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done + +# For the awk script, D is an array of macro values keyed by name, +# likewise P contains macro parameters if any. Preserve backslash +# newline sequences. + +ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* +sed -n ' +s/.\{148\}/&'"$ac_delim"'/g +t rset +:rset +s/^[ ]*#[ ]*define[ ][ ]*/ / +t def +d +:def +s/\\$// +t bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3"/p +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p +d +:bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3\\\\\\n"\\/p +t cont +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p +t cont +d +:cont +n +s/.\{148\}/&'"$ac_delim"'/g +t clear +:clear +s/\\$// +t bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/"/p +d +:bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p +b cont +' >$CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + for (key in D) D_is_set[key] = 1 + FS = "" +} +/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { + line = \$ 0 + split(line, arg, " ") + if (arg[1] == "#") { + defundef = arg[2] + mac1 = arg[3] + } else { + defundef = substr(arg[1], 2) + mac1 = arg[2] + } + split(mac1, mac2, "(") #) + macro = mac2[1] + prefix = substr(line, 1, index(line, defundef) - 1) + if (D_is_set[macro]) { + # Preserve the white space surrounding the "#". + print prefix "define", macro P[macro] D[macro] + next + } else { + # Replace #undef with comments. This is necessary, for example, + # in the case of _POSIX_SOURCE, which is predefined and required + # on some systems where configure will not decide to define it. + if (defundef == "undef") { + print "/*", prefix defundef, macro, "*/" + next + } + } +} +{ print } +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 +fi # test -n "$CONFIG_HEADERS" + + +eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + :H) + # + # CONFIG_HEADER + # + if test x"$ac_file" != x-; then + { + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" + } >"$ac_tmp/config.h" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 +$as_echo "$as_me: $ac_file is unchanged" >&6;} + else + rm -f "$ac_file" + mv "$ac_tmp/config.h" "$ac_file" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + fi + else + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ + || as_fn_error $? "could not create -" "$LINENO" 5 + fi + ;; + + :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} + ;; + esac + + + case $ac_file$ac_mode in + "libtool":C) + + # See if we are running on zsh, and set the options which allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST + fi + + cfgfile="${ofile}T" + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL + +# `$ECHO "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services. +# Generated automatically by $as_me ($PACKAGE$TIMESTAMP) $VERSION +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# NOTE: Changes made to this file will be lost: look at ltmain.sh. +# +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, +# 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is part of GNU Libtool. +# +# GNU Libtool is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# As a special exception to the GNU General Public License, +# if you distribute this file as part of a program or library that +# is built using GNU Libtool, you may include this file under the +# same distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Libtool; see the file COPYING. If not, a copy +# can be downloaded from http://www.gnu.org/licenses/gpl.html, or +# obtained by writing to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +# The names of the tagged configurations supported by this script. +available_tags="CXX " + +# ### BEGIN LIBTOOL CONFIG + +# Which release of libtool.m4 was used? +macro_version=$macro_version +macro_revision=$macro_revision + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# What type of objects to build. +pic_mode=$pic_mode + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# An echo program that protects backslashes. +ECHO=$lt_ECHO + +# The PATH separator for the build system. +PATH_SEPARATOR=$lt_PATH_SEPARATOR + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="\$SED -e 1s/^X//" + +# A grep program that handles long lines. +GREP=$lt_GREP + +# An ERE matcher. +EGREP=$lt_EGREP + +# A literal string matcher. +FGREP=$lt_FGREP + +# A BSD- or MS-compatible name lister. +NM=$lt_NM + +# Whether we need soft or hard links. +LN_S=$lt_LN_S + +# What is the maximum length of a command? +max_cmd_len=$max_cmd_len + +# Object file suffix (normally "o"). +objext=$ac_objext + +# Executable file suffix (normally ""). +exeext=$exeext + +# whether the shell understands "unset". +lt_unset=$lt_unset + +# turn spaces into newlines. +SP2NL=$lt_lt_SP2NL + +# turn newlines into spaces. +NL2SP=$lt_lt_NL2SP + +# convert \$build file names to \$host format. +to_host_file_cmd=$lt_cv_to_host_file_cmd + +# convert \$build files to toolchain format. +to_tool_file_cmd=$lt_cv_to_tool_file_cmd + +# An object symbol dumper. +OBJDUMP=$lt_OBJDUMP + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method = "file_magic". +file_magic_cmd=$lt_file_magic_cmd + +# How to find potential files when deplibs_check_method = "file_magic". +file_magic_glob=$lt_file_magic_glob + +# Find potential files using nocaseglob when deplibs_check_method = "file_magic". +want_nocaseglob=$lt_want_nocaseglob + +# DLL creation program. +DLLTOOL=$lt_DLLTOOL + +# Command to associate shared and link libraries. +sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd + +# The archiver. +AR=$lt_AR + +# Flags to create an archive. +AR_FLAGS=$lt_AR_FLAGS + +# How to feed a file listing to the archiver. +archiver_list_spec=$lt_archiver_list_spec + +# A symbol stripping program. +STRIP=$lt_STRIP + +# Commands used to install an old-style archive. +RANLIB=$lt_RANLIB +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Whether to use a lock for old archive extraction. +lock_old_archive_extraction=$lock_old_archive_extraction + +# A C compiler. +LTCC=$lt_CC + +# LTCC compiler flags. +LTCFLAGS=$lt_CFLAGS + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration. +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair. +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# Transform the output of nm in a C name address pair when lib prefix is needed. +global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix + +# Specify filename containing input files for \$NM. +nm_file_list_spec=$lt_nm_file_list_spec + +# The root where to search for dependent libraries,and in which our libraries should be installed. +lt_sysroot=$lt_sysroot + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# Used to examine libraries when file_magic_cmd begins with "file". +MAGIC_CMD=$MAGIC_CMD + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Manifest tool. +MANIFEST_TOOL=$lt_MANIFEST_TOOL + +# Tool to manipulate archived DWARF debug symbol files on Mac OS X. +DSYMUTIL=$lt_DSYMUTIL + +# Tool to change global to local symbols on Mac OS X. +NMEDIT=$lt_NMEDIT + +# Tool to manipulate fat objects and archives on Mac OS X. +LIPO=$lt_LIPO + +# ldd/readelf like tool for Mach-O binaries on Mac OS X. +OTOOL=$lt_OTOOL + +# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4. +OTOOL64=$lt_OTOOL64 + +# Old archive suffix (normally "a"). +libext=$libext + +# Shared library suffix (normally ".so"). +shrext_cmds=$lt_shrext_cmds + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at link time. +variables_saved_for_relink=$lt_variables_saved_for_relink + +# Do we need the "lib" prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Library versioning type. +version_type=$version_type + +# Shared library runtime path variable. +runpath_var=$runpath_var + +# Shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Permission mode override for installation of shared libraries. +install_override_mode=$lt_install_override_mode + +# Command to use after installation of a shared archive. +postinstall_cmds=$lt_postinstall_cmds + +# Command to use after uninstallation of a shared archive. +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# As "finish_cmds", except a single script fragment to be evaled but +# not shown. +finish_eval=$lt_finish_eval + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Compile-time system search path for libraries. +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries. +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + + +# The linker used to build libraries. +LD=$lt_LD + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# Commands used to build an old-style archive. +old_archive_cmds=$lt_old_archive_cmds + +# A language specific compiler. +CC=$lt_compiler + +# Is the compiler the GNU compiler? +with_gcc=$GCC + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc + +# Whether or not to disallow shared libs when runtime libs are static. +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec + +# Whether the compiler copes with passing no objects directly. +compiler_needs_object=$lt_compiler_needs_object + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds + +# Commands used to build a shared archive. +archive_cmds=$lt_archive_cmds +archive_expsym_cmds=$lt_archive_expsym_cmds + +# Commands used to build a loadable module if different from building +# a shared archive. +module_cmds=$lt_module_cmds +module_expsym_cmds=$lt_module_expsym_cmds + +# Whether we are building with GNU ld or not. +with_gnu_ld=$lt_with_gnu_ld + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag + +# Flag that enforces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec + +# Whether we need a single "-rpath" flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator + +# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes +# DIR into the resulting binary. +hardcode_direct=$hardcode_direct + +# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes +# DIR into the resulting binary and the resulting library dependency is +# "absolute",i.e impossible to change by setting \${shlibpath_var} if the +# library is relocated. +hardcode_direct_absolute=$hardcode_direct_absolute + +# Set to "yes" if using the -LDIR flag during linking hardcodes DIR +# into the resulting binary. +hardcode_minus_L=$hardcode_minus_L + +# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR +# into the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var + +# Set to "yes" if building a shared library automatically hardcodes DIR +# into the library and all subsequent libraries and executables linked +# against it. +hardcode_automatic=$hardcode_automatic + +# Set to yes if linker adds runtime paths of dependent libraries +# to runtime path list. +inherit_rpath=$inherit_rpath + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs + +# Set to "yes" if exported symbols are required. +always_export_symbols=$always_export_symbols + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms + +# Commands necessary for linking programs (against libraries) with templates. +prelink_cmds=$lt_prelink_cmds + +# Commands necessary for finishing linking programs. +postlink_cmds=$lt_postlink_cmds + +# Specify filename containing input files. +file_list_spec=$lt_file_list_spec + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action + +# The directories searched by this compiler when creating a shared library. +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs + +# Dependencies to place before and after the objects being linked to +# create a shared library. +predep_objects=$lt_predep_objects +postdep_objects=$lt_postdep_objects +predeps=$lt_predeps +postdeps=$lt_postdeps + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path + +# ### END LIBTOOL CONFIG + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + +ltmain="$ac_aux_dir/ltmain.sh" + + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + if test x"$xsi_shell" = xyes; then + sed -e '/^func_dirname ()$/,/^} # func_dirname /c\ +func_dirname ()\ +{\ +\ case ${1} in\ +\ */*) func_dirname_result="${1%/*}${2}" ;;\ +\ * ) func_dirname_result="${3}" ;;\ +\ esac\ +} # Extended-shell func_dirname implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_basename ()$/,/^} # func_basename /c\ +func_basename ()\ +{\ +\ func_basename_result="${1##*/}"\ +} # Extended-shell func_basename implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_dirname_and_basename ()$/,/^} # func_dirname_and_basename /c\ +func_dirname_and_basename ()\ +{\ +\ case ${1} in\ +\ */*) func_dirname_result="${1%/*}${2}" ;;\ +\ * ) func_dirname_result="${3}" ;;\ +\ esac\ +\ func_basename_result="${1##*/}"\ +} # Extended-shell func_dirname_and_basename implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_stripname ()$/,/^} # func_stripname /c\ +func_stripname ()\ +{\ +\ # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are\ +\ # positional parameters, so assign one to ordinary parameter first.\ +\ func_stripname_result=${3}\ +\ func_stripname_result=${func_stripname_result#"${1}"}\ +\ func_stripname_result=${func_stripname_result%"${2}"}\ +} # Extended-shell func_stripname implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_split_long_opt ()$/,/^} # func_split_long_opt /c\ +func_split_long_opt ()\ +{\ +\ func_split_long_opt_name=${1%%=*}\ +\ func_split_long_opt_arg=${1#*=}\ +} # Extended-shell func_split_long_opt implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_split_short_opt ()$/,/^} # func_split_short_opt /c\ +func_split_short_opt ()\ +{\ +\ func_split_short_opt_arg=${1#??}\ +\ func_split_short_opt_name=${1%"$func_split_short_opt_arg"}\ +} # Extended-shell func_split_short_opt implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_lo2o ()$/,/^} # func_lo2o /c\ +func_lo2o ()\ +{\ +\ case ${1} in\ +\ *.lo) func_lo2o_result=${1%.lo}.${objext} ;;\ +\ *) func_lo2o_result=${1} ;;\ +\ esac\ +} # Extended-shell func_lo2o implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_xform ()$/,/^} # func_xform /c\ +func_xform ()\ +{\ + func_xform_result=${1%.*}.lo\ +} # Extended-shell func_xform implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_arith ()$/,/^} # func_arith /c\ +func_arith ()\ +{\ + func_arith_result=$(( $* ))\ +} # Extended-shell func_arith implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_len ()$/,/^} # func_len /c\ +func_len ()\ +{\ + func_len_result=${#1}\ +} # Extended-shell func_len implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + +fi + +if test x"$lt_shell_append" = xyes; then + sed -e '/^func_append ()$/,/^} # func_append /c\ +func_append ()\ +{\ + eval "${1}+=\\${2}"\ +} # Extended-shell func_append implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + sed -e '/^func_append_quoted ()$/,/^} # func_append_quoted /c\ +func_append_quoted ()\ +{\ +\ func_quote_for_eval "${2}"\ +\ eval "${1}+=\\\\ \\$func_quote_for_eval_result"\ +} # Extended-shell func_append_quoted implementation' "$cfgfile" > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") +test 0 -eq $? || _lt_function_replace_fail=: + + + # Save a `func_append' function call where possible by direct use of '+=' + sed -e 's%func_append \([a-zA-Z_]\{1,\}\) "%\1+="%g' $cfgfile > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") + test 0 -eq $? || _lt_function_replace_fail=: +else + # Save a `func_append' function call even when '+=' is not available + sed -e 's%func_append \([a-zA-Z_]\{1,\}\) "%\1="$\1%g' $cfgfile > $cfgfile.tmp \ + && mv -f "$cfgfile.tmp" "$cfgfile" \ + || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp") + test 0 -eq $? || _lt_function_replace_fail=: +fi + +if test x"$_lt_function_replace_fail" = x":"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Unable to substitute extended shell functions in $ofile" >&5 +$as_echo "$as_me: WARNING: Unable to substitute extended shell functions in $ofile" >&2;} +fi + + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" + + + cat <<_LT_EOF >> "$ofile" + +# ### BEGIN LIBTOOL TAG CONFIG: CXX + +# The linker used to build libraries. +LD=$lt_LD_CXX + +# How to create reloadable object files. +reload_flag=$lt_reload_flag_CXX +reload_cmds=$lt_reload_cmds_CXX + +# Commands used to build an old-style archive. +old_archive_cmds=$lt_old_archive_cmds_CXX + +# A language specific compiler. +CC=$lt_compiler_CXX + +# Is the compiler the GNU compiler? +with_gcc=$GCC_CXX + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_CXX + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic_CXX + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl_CXX + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static_CXX + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o_CXX + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc_CXX + +# Whether or not to disallow shared libs when runtime libs are static. +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_CXX + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_CXX + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec_CXX + +# Whether the compiler copes with passing no objects directly. +compiler_needs_object=$lt_compiler_needs_object_CXX + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_CXX + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_CXX + +# Commands used to build a shared archive. +archive_cmds=$lt_archive_cmds_CXX +archive_expsym_cmds=$lt_archive_expsym_cmds_CXX + +# Commands used to build a loadable module if different from building +# a shared archive. +module_cmds=$lt_module_cmds_CXX +module_expsym_cmds=$lt_module_expsym_cmds_CXX + +# Whether we are building with GNU ld or not. +with_gnu_ld=$lt_with_gnu_ld_CXX + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag_CXX + +# Flag that enforces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag_CXX + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_CXX + +# Whether we need a single "-rpath" flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator_CXX + +# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes +# DIR into the resulting binary. +hardcode_direct=$hardcode_direct_CXX + +# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes +# DIR into the resulting binary and the resulting library dependency is +# "absolute",i.e impossible to change by setting \${shlibpath_var} if the +# library is relocated. +hardcode_direct_absolute=$hardcode_direct_absolute_CXX + +# Set to "yes" if using the -LDIR flag during linking hardcodes DIR +# into the resulting binary. +hardcode_minus_L=$hardcode_minus_L_CXX + +# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR +# into the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var_CXX + +# Set to "yes" if building a shared library automatically hardcodes DIR +# into the library and all subsequent libraries and executables linked +# against it. +hardcode_automatic=$hardcode_automatic_CXX + +# Set to yes if linker adds runtime paths of dependent libraries +# to runtime path list. +inherit_rpath=$inherit_rpath_CXX + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs_CXX + +# Set to "yes" if exported symbols are required. +always_export_symbols=$always_export_symbols_CXX + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds_CXX + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms_CXX + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms_CXX + +# Commands necessary for linking programs (against libraries) with templates. +prelink_cmds=$lt_prelink_cmds_CXX + +# Commands necessary for finishing linking programs. +postlink_cmds=$lt_postlink_cmds_CXX + +# Specify filename containing input files. +file_list_spec=$lt_file_list_spec_CXX + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action_CXX + +# The directories searched by this compiler when creating a shared library. +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_CXX + +# Dependencies to place before and after the objects being linked to +# create a shared library. +predep_objects=$lt_predep_objects_CXX +postdep_objects=$lt_postdep_objects_CXX +predeps=$lt_predeps_CXX +postdeps=$lt_postdeps_CXX + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path_CXX + +# ### END LIBTOOL TAG CONFIG: CXX +_LT_EOF + + ;; + "svn_private_config.h.tmp":C) svn_cf=subversion/svn_private_config.h; + $SED -e "s/@SVN_DB_HEADER@/$SVN_DB_HEADER/" $svn_cf.tmp > $svn_cf.tmp.new + cmp -s $svn_cf.tmp.new $svn_cf || mv -f $svn_cf.tmp.new $svn_cf + rm -f $svn_cf.tmp.new $svn_cf.tmp ;; + "tools/backup/hot-backup.py":F) chmod +x tools/backup/hot-backup.py ;; + "tools/hook-scripts/commit-access-control.pl":F) chmod +x tools/hook-scripts/commit-access-control.pl ;; + "subversion/bindings/swig/perl/native/Makefile.PL":F) chmod +x subversion/bindings/swig/perl/native/Makefile.PL ;; + "packages/solaris/pkginfo":F) chmod +x packages/solaris/pkginfo ;; + + esac +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + +# ==== Print final messages to user ========================================== + + +if test "$svn_lib_berkeley_db" = "no" && test "$with_berkeley_db" != "no"; then + db_version="$SVN_FS_WANT_DB_MAJOR.$SVN_FS_WANT_DB_MINOR.$SVN_FS_WANT_DB_PATCH" + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: we have configured without BDB filesystem support + + +You don't seem to have Berkeley DB version $db_version or newer +installed and linked to APR-UTIL. We have created a Makefile which will build +Subversion without support for the Berkeley DB back-end. You can find the +latest version of Berkeley DB here: + + http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html + +or explicitly specify --without-berkeley-db to silence this warning. +" >&5 +$as_echo "$as_me: WARNING: we have configured without BDB filesystem support + + +You don't seem to have Berkeley DB version $db_version or newer +installed and linked to APR-UTIL. We have created a Makefile which will build +Subversion without support for the Berkeley DB back-end. You can find the +latest version of Berkeley DB here: + + http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html + +or explicitly specify --without-berkeley-db to silence this warning. +" >&2;} +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000000..b1748ce9e531 --- /dev/null +++ b/configure.ac @@ -0,0 +1,1521 @@ +dnl Licensed to the Apache Software Foundation (ASF) under one +dnl or more contributor license agreements. See the NOTICE file +dnl distributed with this work for additional information +dnl regarding copyright ownership. The ASF licenses this file +dnl to you under the Apache License, Version 2.0 (the +dnl "License"); you may not use this file except in compliance +dnl with the License. You may obtain a copy of the License at +dnl +dnl http://www.apache.org/licenses/LICENSE-2.0 +dnl +dnl Unless required by applicable law or agreed to in writing, +dnl software distributed under the License is distributed on an +dnl "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +dnl KIND, either express or implied. See the License for the +dnl specific language governing permissions and limitations +dnl under the License. + +dnl configure.ac: Autoconfiscation for Subversion +dnl Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.59) + +dnl Get the version of Subversion, using m4's esyscmd() command to do this +dnl at m4-time, since AC_INIT() requires it then. +AC_INIT([subversion], + [esyscmd(python build/getversion.py SVN subversion/include/svn_version.h)], + [http://subversion.apache.org/]) + +AC_CONFIG_SRCDIR(subversion/include/svn_types.h) +AC_CONFIG_AUX_DIR([build]) + +AC_MSG_NOTICE([Configuring Subversion ]AC_PACKAGE_VERSION) + +AC_SUBST([abs_srcdir], ["`cd $srcdir && pwd`"]) +AC_SUBST([abs_builddir], ["`pwd`"]) +if test "$abs_srcdir" = "$abs_builddir"; then + canonicalized_srcdir="" +else + canonicalized_srcdir="$srcdir/" +fi +AC_SUBST([canonicalized_srcdir]) + +SWIG_LDFLAGS="$LDFLAGS" +AC_SUBST([SWIG_LDFLAGS]) + +# Generate config.nice early (before the arguments are munged) +SVN_CONFIG_NICE(config.nice) + +# ==== Check for programs ==================================================== + +# Look for a C compiler (before anything can set CFLAGS) +CMAINTAINERFLAGS="$CUSERFLAGS" +CUSERFLAGS="$CFLAGS" +AC_PROG_CC +SVN_CC_MODE_SETUP + +# Look for a C++ compiler (before anything can set CXXFLAGS) +CXXMAINTAINERFLAGS="$CXXUSERFLAGS" +CXXUSERFLAGS="$CXXFLAGS" +AC_PROG_CXX +SVN_CXX_MODE_SETUP + +# Look for a C pre-processor +AC_PROG_CPP + +# Look for a good sed +# AC_PROG_SED was introduced in Autoconf 2.59b +m4_ifdef([AC_PROG_SED], [AC_PROG_SED], [SED="${SED:-sed}"]) + +# Grab target_cpu, so we can use it in the Solaris pkginfo file +AC_CANONICAL_TARGET + +# Look for an extended grep +AC_PROG_EGREP + +AC_PROG_LN_S + +AC_PROG_INSTALL +# If $INSTALL is relative path to our fallback install-sh, then convert +# to an absolute path, as in some cases (e.g. Solaris VPATH build), libtool +# may try to use it from a changed working directory. +if test "$INSTALL" = "build/install-sh -c"; then + INSTALL="$abs_srcdir/$INSTALL" +fi + +if test -z "$MKDIR"; then + MKDIR="$INSTALL -d" +fi +AC_SUBST([MKDIR]) + +# ==== Libraries, for which we may have source to build ====================== + +dnl verify apr version and set apr flags +dnl These regular expressions should not contain "\(" and "\)". +dnl The specific reason we require APR 0.9.7 is: +dnl It contains fixes to its file writing routines +dnl now generating errors instead of silently ignoring +dnl them. Only .7 and later can guarantee repository +dnl integrity with FSFS. + +APR_VER_REGEXES=["0\.9\.[7-9] 0\.9\.1[0-9] 1\. 2\."] + +SVN_LIB_APR($APR_VER_REGEXES) + +if test `expr $apr_version : 2` -ne 0; then + dnl Bump the library so-version to 2 if using APR-2 + dnl (Debian uses so-version 1 for APR-1-with-largefile) + svn_lib_ver=2 + dnl APR-2 provides APRUTIL + apu_config=$apr_config + AC_SUBST(SVN_APRUTIL_INCLUDES) + AC_SUBST(SVN_APRUTIL_CONFIG, ["$apu_config"]) + AC_SUBST(SVN_APRUTIL_LIBS) +else + svn_lib_ver=0 + APU_VER_REGEXES=["0\.9\.[7-9] 0\.9\.1[0-9] 1\."] + SVN_LIB_APRUTIL($APU_VER_REGEXES) +fi +SVN_LT_SOVERSION="-version-info $svn_lib_ver" +AC_SUBST(SVN_LT_SOVERSION) +AC_DEFINE_UNQUOTED(SVN_SOVERSION, $svn_lib_ver, + [Subversion library major verson]) + +dnl Search for pkg-config +AC_PATH_PROG(PKG_CONFIG, pkg-config) + +dnl Search for serf +SVN_LIB_SERF(1,2,1) + +if test "$svn_lib_serf" = "yes"; then + AC_DEFINE([SVN_HAVE_SERF], 1, + [Defined if support for Serf is enabled]) +fi + +dnl Search for apr_memcache (only affects fs_fs) +SVN_LIB_APR_MEMCACHE + +if test "$svn_lib_apr_memcache" = "yes"; then + AC_DEFINE(SVN_HAVE_MEMCACHE, 1, + [Defined if apr_memcache (standalone or in apr-util) is present]) +fi + + +dnl Find Apache with a recent-enough magic module number +SVN_FIND_APACHE(20020903) + +dnl Search for SQLite. If you change SQLITE_URL from a .zip to +dnl something else also update build/ac-macros/sqlite.m4 to reflect +dnl the correct command to unpack the downloaded file. +SQLITE_MINIMUM_VER="3.7.12" +SQLITE_RECOMMENDED_VER="3.7.15.1" +SQLITE_URL="http://www.sqlite.org/sqlite-amalgamation-$(printf %d%02d%02d%02d $(echo ${SQLITE_RECOMMENDED_VER} | sed -e 's/\./ /g')).zip" + +SVN_LIB_SQLITE(${SQLITE_MINIMUM_VER}, ${SQLITE_RECOMMENDED_VER}, + ${SQLITE_URL}) + +AC_ARG_ENABLE(sqlite-compatibility-version, + AS_HELP_STRING([--enable-sqlite-compatibility-version=X.Y.Z], + [Allow binary to run against SQLite as old as ARG]), + [sqlite_compat_ver=$enableval],[sqlite_compat_ver=no]) + +if test -n "$sqlite_compat_ver" && test "$sqlite_compat_ver" != no; then + SVN_SQLITE_VERNUM_PARSE([$sqlite_compat_ver], + [sqlite_compat_ver_num]) + CFLAGS="-DSVN_SQLITE_MIN_VERSION='\"$sqlite_compat_ver\"' $CFLAGS" + CFLAGS="-DSVN_SQLITE_MIN_VERSION_NUMBER=$sqlite_compat_ver_num $CFLAGS" +fi + +SVN_CHECK_FOR_ATOMIC_BUILTINS + +if test "$svn_cv_atomic_builtins" = "yes"; then + AC_DEFINE(SVN_HAS_ATOMIC_BUILTINS, 1, [Define if compiler provides atomic builtins]) +fi + +dnl Set up a number of directories --------------------- + +dnl Create SVN_BINDIR for proper substitution +if test "${bindir}" = '${exec_prefix}/bin'; then + if test "${exec_prefix}" = "NONE"; then + if test "${prefix}" = "NONE"; then + SVN_BINDIR="${ac_default_prefix}/bin" + else + SVN_BINDIR="${prefix}/bin" + fi + else + SVN_BINDIR="${exec_prefix}/bin" + fi +else + SVN_BINDIR="${bindir}" +fi + +dnl fully evaluate this value. when we substitute it into our tool scripts, +dnl they will not have things such as ${bindir} available +SVN_BINDIR="`eval echo ${SVN_BINDIR}`" +AC_SUBST(SVN_BINDIR) + +dnl provide ${bindir} in svn_private_config.h for use in compiled code +AC_DEFINE_UNQUOTED(SVN_BINDIR, "${SVN_BINDIR}", + [Defined to be the path to the installed binaries]) + +dnl This purposely does *not* allow for multiple parallel installs. +dnl However, it is compatible with most gettext usages. +localedir='${datadir}/locale' +AC_SUBST(localedir) + +dnl For SVN_LOCALE_DIR, we have to expand it to something. See SVN_BINDIR. +if test "${datadir}" = '${prefix}/share' && test "${prefix}" = "NONE"; then + exp_localedir='${ac_default_prefix}/share/locale' +else + exp_localedir=$localedir +fi +SVN_EXPAND_VAR(svn_localedir, "${exp_localedir}") +AC_DEFINE_UNQUOTED(SVN_LOCALE_DIR, "${svn_localedir}", + [Defined to be the path to the installed locale dirs]) + +dnl Check for libtool -- we'll definitely need it for all our shared libs! +AC_MSG_NOTICE([configuring libtool now]) +ifdef([LT_INIT], [LT_INIT], [AC_PROG_LIBTOOL]) +AC_ARG_ENABLE(experimental-libtool, + AS_HELP_STRING([--enable-experimental-libtool],[Use APR's libtool]), + [experimental_libtool=$enableval],[experimental_libtool=no]) + +if test "$experimental_libtool" = "yes"; then + echo "using APR's libtool" + sh_libtool="`$apr_config --apr-libtool`" + LIBTOOL="$sh_libtool" + SVN_LIBTOOL="$sh_libtool" +else + sh_libtool="$abs_builddir/libtool" + SVN_LIBTOOL="\$(SHELL) $sh_libtool" +fi +AC_SUBST(SVN_LIBTOOL) + +dnl Determine the libtool version +changequote(, )dnl +lt_pversion=`$LIBTOOL --version 2>/dev/null|$SED -e 's/([^)]*)//g;s/^[^0-9]*//;s/[- ].*//g;q'` +lt_version=`echo $lt_pversion|$SED -e 's/\([a-z]*\)$/.\1/'` +lt_major_version=`echo $lt_version | cut -d'.' -f 1` +changequote([, ])dnl + +dnl set the default parameters +svn_enable_static=yes +svn_enable_shared=yes + +dnl check for --enable-static option +AC_ARG_ENABLE(static, + AS_HELP_STRING([--enable-static], + [Build static libraries]), + [svn_enable_static="$enableval"], [svn_enable_static="yes"]) + +dnl check for --enable-shared option +AC_ARG_ENABLE(shared, + AS_HELP_STRING([--enable-shared], + [Build shared libraries]), + [svn_enable_shared="$enableval"], [svn_enable_shared="yes"]) + +if test "$svn_enable_static" = "yes" && test "$svn_enable_shared" = "yes" ; then + AC_MSG_NOTICE([building both shared and static libraries]) +elif test "$svn_enable_static" = "yes" ; then + AC_MSG_NOTICE([building static libraries only]) + LT_CFLAGS="-static $LT_CFLAGS" + LT_LDFLAGS="-static $LT_LDFLAGS" +elif test "$svn_enable_shared" = "yes" ; then + AC_MSG_NOTICE([building shared libraries only]) + if test "$lt_major_version" = "1" ; then + LT_CFLAGS="-prefer-pic $LT_CFLAGS" + elif test "$lt_major_version" = "2" ; then + LT_CFLAGS="-shared $LT_CFLAGS" + fi + LT_LDFLAGS="-shared $LT_LDFLAGS" +else + AC_MSG_ERROR([cannot disable both shared and static libraries]) +fi + +dnl Check for --enable-all-static option +AC_ARG_ENABLE(all-static, + AS_HELP_STRING([--enable-all-static], + [Build completely static (standalone) binaries.]), + [ + if test "$enableval" = "yes" ; then + LT_LDFLAGS="-all-static $LT_LDFLAGS" + elif test "$enableval" != "no" ; then + AC_MSG_ERROR([--enable-all-static doesn't accept argument]) + fi +]) + +AC_SUBST(LT_CFLAGS) +AC_SUBST(LT_LDFLAGS) + +AC_ARG_ENABLE(local-library-preloading, + AS_HELP_STRING([--enable-local-library-preloading], + [Enable preloading of locally built libraries in locally + built executables. This may be necessary for testing + prior to installation on some platforms. It does not + work on some platforms (Darwin, OpenBSD, ...).]), + [ + if test "$enableval" != "no"; then + if test "$svn_enable_shared" = "yes"; then + TRANSFORM_LIBTOOL_SCRIPTS="transform-libtool-scripts" + else + AC_MSG_ERROR([--enable-local-library-preloading conflicts with --disable-shared]) + fi + else + TRANSFORM_LIBTOOL_SCRIPTS="" + fi + ], [ + TRANSFORM_LIBTOOL_SCRIPTS="" +]) +AC_SUBST(TRANSFORM_LIBTOOL_SCRIPTS) + +dnl Check if -no-undefined is needed for the platform. +dnl It should always work but with libtool 1.4.3 on OS X it breaks the build. +dnl So we only turn it on for platforms where we know we really need it. +AC_MSG_CHECKING([whether libtool needs -no-undefined]) +case $host in + *-*-cygwin*) + AC_MSG_RESULT([yes]) + LT_NO_UNDEFINED="-no-undefined" + ;; + *) + AC_MSG_RESULT([no]) + LT_NO_UNDEFINED="" + ;; +esac +AC_SUBST(LT_NO_UNDEFINED) + +AC_MSG_CHECKING([whether to avoid circular linkage at all costs]) +case $host in + *-*-cygwin*) + AC_MSG_RESULT([yes]) + AC_DEFINE([SVN_AVOID_CIRCULAR_LINKAGE_AT_ALL_COSTS_HACK], 1, + [Define if circular linkage is not possible on this platform.]) + ;; + *) + AC_MSG_RESULT([no]) + ;; +esac + +dnl Check for trang. +trang=yes +AC_ARG_WITH(trang, +AS_HELP_STRING([--with-trang=PATH], + [Specify the command to run the trang schema converter]), +[ + trang="$withval" +]) +if test "$trang" = "yes"; then + AC_PATH_PROG(TRANG, trang, none) +else + TRANG="$trang" + AC_SUBST(TRANG) +fi + +dnl Check for doxygen +doxygen=yes +AC_ARG_WITH(doxygen, +AC_HELP_STRING([--with-doxygen=PATH], + [Specify the command to run doxygen]), +[ + doxygen="$withval" +]) +if test "$doxygen" = "yes"; then + AC_PATH_PROG(DOXYGEN, doxygen, none) +else + DOXYGEN="$doxygen" + AC_SUBST(DOXYGEN) +fi + + +dnl Check for libraries -------------------- + +dnl Expat ------------------- + +AC_ARG_WITH(expat, + AS_HELP_STRING([--with-expat=INCLUDES:LIB_SEARCH_DIRS:LIBS], + [Specify location of Expat]), + [svn_lib_expat="$withval"], + [svn_lib_expat="::expat"]) + +# APR-util accepts "builtin" as an argument to this option so if the user +# passed "builtin" pretend the user didn't specify the --with-expat option +# at all. Expat will (hopefully) be found in apr-util. +test "_$svn_lib_expat" = "_builtin" && svn_lib_expat="::expat" + +AC_MSG_CHECKING([for Expat]) +if test -n "`echo "$svn_lib_expat" | $EGREP ":.*:"`"; then + SVN_XML_INCLUDES="" + for i in [`echo "$svn_lib_expat" | $SED -e "s/\([^:]*\):.*/\1/"`]; do + SVN_XML_INCLUDES="$SVN_XML_INCLUDES -I$i" + done + SVN_XML_INCLUDES="${SVN_XML_INCLUDES## }" + for l in [`echo "$svn_lib_expat" | $SED -e "s/.*:\([^:]*\):.*/\1/"`]; do + LDFLAGS="$LDFLAGS -L$l" + done + for l in [`echo "$svn_lib_expat" | $SED -e "s/.*:\([^:]*\)/\1/"`]; do + SVN_XML_LIBS="$SVN_XML_LIBS -l$l" + done + SVN_XML_LIBS="${SVN_XML_LIBS## }" + old_CPPFLAGS="$CPPFLAGS" + old_LIBS="$LIBS" + CPPFLAGS="$CPPFLAGS $SVN_XML_INCLUDES" + LIBS="$LIBS $SVN_XML_LIBS" + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +#include +int main() +{XML_ParserCreate(NULL);}]])], svn_lib_expat="yes", svn_lib_expat="no") + LIBS="$old_LIBS" + if test "$svn_lib_expat" = "yes"; then + AC_MSG_RESULT([yes]) + else + SVN_XML_INCLUDES="" + SVN_XML_LIBS="" + CPPFLAGS="$CPPFLAGS $SVN_APRUTIL_INCLUDES" + if test "$enable_all_static" != "yes"; then + SVN_APRUTIL_LIBS="$SVN_APRUTIL_LIBS `$apu_config --libs`" + fi + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#include +int main() +{XML_ParserCreate(NULL);}]])], svn_lib_expat="yes", svn_lib_expat="no") + if test "$svn_lib_expat" = "yes"; then + AC_MSG_RESULT([yes]) + AC_MSG_WARN([Expat found amongst libraries used by APR-Util, but Subversion libraries might be needlessly linked against additional unused libraries. It can be avoided by specifying exact location of Expat in argument of --with-expat option.]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([Expat not found]) + fi + fi + CPPFLAGS="$old_CPPFLAGS" +else + AC_MSG_RESULT([no]) + if test "$svn_lib_expat" = "yes"; then + AC_MSG_ERROR([--with-expat option requires argument]) + elif test "$svn_lib_expat" = "no"; then + AC_MSG_ERROR([Expat is required]) + else + AC_MSG_ERROR([Invalid syntax of argument of --with-expat option]) + fi +fi +AC_SUBST(SVN_XML_INCLUDES) +AC_SUBST(SVN_XML_LIBS) + +dnl Berkeley DB ------------------- + +# Berkeley DB on SCO OpenServer needs -lsocket +AC_CHECK_LIB(socket, socket) + +# Build the BDB filesystem library only if we have an appropriate +# version of Berkeley DB. +case "$host" in +powerpc-apple-darwin*) + # Berkeley DB 4.0 does not work on OS X. + SVN_FS_WANT_DB_MAJOR=4 + SVN_FS_WANT_DB_MINOR=1 + SVN_FS_WANT_DB_PATCH=25 + ;; +*) + SVN_FS_WANT_DB_MAJOR=4 + SVN_FS_WANT_DB_MINOR=0 + SVN_FS_WANT_DB_PATCH=14 + ;; +esac +# Look for libdb4.so first: +SVN_LIB_BERKELEY_DB($SVN_FS_WANT_DB_MAJOR, $SVN_FS_WANT_DB_MINOR, + $SVN_FS_WANT_DB_PATCH, [db4 db]) + +AC_DEFINE_UNQUOTED(SVN_FS_WANT_DB_MAJOR, $SVN_FS_WANT_DB_MAJOR, + [The desired major version for the Berkeley DB]) +AC_DEFINE_UNQUOTED(SVN_FS_WANT_DB_MINOR, $SVN_FS_WANT_DB_MINOR, + [The desired minor version for the Berkeley DB]) +AC_DEFINE_UNQUOTED(SVN_FS_WANT_DB_PATCH, $SVN_FS_WANT_DB_PATCH, + [The desired patch version for the Berkeley DB]) + +AC_SUBST(SVN_DB_INCLUDES) +AC_SUBST(SVN_DB_LIBS) + +SVN_LIB_SASL + +if test "$svn_lib_sasl" = "yes"; then + AC_DEFINE(SVN_HAVE_SASL, 1, + [Defined if Cyrus SASL v2 is present on the system]) +fi + +dnl Mac OS specific features ------------------- + +SVN_LIB_MACHO_ITERATE +SVN_LIB_MACOS_PLIST +SVN_LIB_MACOS_KEYCHAIN + +dnl APR_HAS_DSO ------------------- + +AC_MSG_CHECKING([whether APR has support for DSOs]) +old_CPPFLAGS="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES" +AC_PREPROC_IFELSE([AC_LANG_SOURCE([[ +#include +#if !APR_HAS_DSO +#error +#endif]])], + APR_HAS_DSO="yes" + AC_MSG_RESULT([yes]), + APR_HAS_DSO="no" + AC_MSG_RESULT([no])) +CPPFLAGS="$old_CPPFLAGS" + + +dnl D-Bus (required for support for KWallet) ------------------- + +if test -n "$PKG_CONFIG"; then + AC_MSG_CHECKING([for D-Bus .pc file]) + if $PKG_CONFIG --exists dbus-1; then + AC_MSG_RESULT([yes]) + old_CPPFLAGS="$CPPFLAGS" + old_LIBS="$LIBS" + DBUS_CPPFLAGS="`$PKG_CONFIG --cflags dbus-1`" + AC_MSG_CHECKING([D-Bus version]) + DBUS_VERSION="`$PKG_CONFIG --modversion dbus-1`" + AC_MSG_RESULT([$DBUS_VERSION]) + # D-Bus 0.* requires DBUS_API_SUBJECT_TO_CHANGE + if test -n ["`echo "$DBUS_VERSION" | $EGREP '^0\.[[:digit:]]+'`"]; then + DBUS_CPPFLAGS="$DBUS_CPPFLAGS -DDBUS_API_SUBJECT_TO_CHANGE" + fi + DBUS_LIBS="`$PKG_CONFIG --libs dbus-1`" + CPPFLAGS="$CPPFLAGS $DBUS_CPPFLAGS" + LIBS="$LIBS $DBUS_LIBS" + AC_MSG_CHECKING([for D-Bus]) + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +#include +int main() +{dbus_bus_get(DBUS_BUS_SESSION, NULL);}]])], HAVE_DBUS="yes", HAVE_DBUS="no") + if test "$HAVE_DBUS" = "yes"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + CPPFLAGS="$old_CPPFLAGS" + LIBS="$old_LIBS" + else + AC_MSG_RESULT([no]) + fi +fi + +dnl GPG Agent ------------------- + +AC_ARG_WITH(gpg_agent, +AS_HELP_STRING([--without-gpg-agent], + [Disable support for GPG-Agent]), + [], [with_gpg_agent=yes]) +AC_MSG_CHECKING([whether to support GPG-Agent]) +if test "$with_gpg_agent" = "yes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE([SVN_HAVE_GPG_AGENT], [1], + [Is GPG Agent support enabled?]) +else + AC_MSG_RESULT([no]) +fi + +AC_SUBST(SVN_HAVE_GPG_AGENT) + +dnl GNOME Keyring ------------------- + +AC_ARG_WITH(gnome_keyring, + AS_HELP_STRING([--with-gnome-keyring], + [Enable use of GNOME Keyring for auth credentials (enabled by default if found)]), + [with_gnome_keyring="$withval"], + [with_gnome_keyring=auto]) + +found_gnome_keyring=no +AC_MSG_CHECKING([whether to look for GNOME Keyring]) +if test "$with_gnome_keyring" != "no"; then + AC_MSG_RESULT([yes]) + if test "$svn_enable_shared" = "yes"; then + if test "$APR_HAS_DSO" = "yes"; then + if test -n "$PKG_CONFIG"; then + AC_MSG_CHECKING([for GLib and GNOME Keyring .pc files]) + if $PKG_CONFIG --exists glib-2.0 gnome-keyring-1; then + AC_MSG_RESULT([yes]) + old_CPPFLAGS="$CPPFLAGS" + SVN_GNOME_KEYRING_INCLUDES="`$PKG_CONFIG --cflags glib-2.0 gnome-keyring-1`" + CPPFLAGS="$CPPFLAGS $SVN_GNOME_KEYRING_INCLUDES" + AC_CHECK_HEADER(gnome-keyring.h, found_gnome_keyring=yes, found_gnome_keyring=no) + AC_MSG_CHECKING([for GNOME Keyring]) + if test "$found_gnome_keyring" = "yes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE([SVN_HAVE_GNOME_KEYRING], [1], + [Is GNOME Keyring support enabled?]) + CPPFLAGS="$old_CPPFLAGS" + SVN_GNOME_KEYRING_LIBS="`$PKG_CONFIG --libs glib-2.0 gnome-keyring-1`" + else + AC_MSG_RESULT([no]) + if test "$with_gnome_keyring" = "yes"; then + AC_MSG_ERROR([cannot find GNOME Keyring]) + fi + fi + else + AC_MSG_RESULT([no]) + if test "$with_gnome_keyring" = "yes"; then + AC_MSG_ERROR([cannot find GLib and GNOME Keyring .pc files.]) + else + with_gnome_keyring=no + fi + fi + else + if test "$with_gnome_keyring" = "yes"; then + AC_MSG_ERROR([cannot find pkg-config. GNOME Keyring requires this.]) + else + with_gnome_keyring=no + fi + fi + else + if test "$with_gnome_keyring" = "yes"; then + AC_MSG_ERROR([APR does not have support for DSOs. GNOME Keyring requires this.]) + else + with_gnome_keyring=no + fi + fi + else + if test "$with_gnome_keyring" = "yes"; then + AC_MSG_ERROR([--with-gnome-keyring conflicts with --disable-shared]) + else + with_gnome_keyring=no + fi + fi +else + AC_MSG_RESULT([no]) +fi +AC_SUBST(SVN_GNOME_KEYRING_INCLUDES) +AC_SUBST(SVN_GNOME_KEYRING_LIBS) + + +dnl Ev2 experimental features ---------------------- +dnl Note: The Ev2 implementations will be built unconditionally, but by +dnl providing this flag, users can choose to use the currently-shimmed Ev2 +dnl editor implementations for various operations. This will probably +dnl negatively impact performance, but is useful for testing. +AC_ARG_ENABLE(ev2-impl, + AS_HELP_STRING([--enable-ev2-impl], + [Use Ev2 implementations, where available [EXPERIMENTAL]]), + [enable_ev2_impl=$enableval],[enable_ev2_impl=no]) +if test "$enable_ev2_impl" = "yes"; then + AC_DEFINE(ENABLE_EV2_IMPL, 1, + [Define to 1 if Ev2 implementations should be used.]) +fi + + +dnl I18n ------------------- + +AC_ARG_ENABLE(nls, + AS_HELP_STRING([--disable-nls],[Disable gettext functionality]), + [enable_nls=$enableval],[enable_nls=yes]) + +USE_NLS="no" +if test "$enable_nls" = "yes"; then + dnl First, check to see if there is a working msgfmt. + AC_PATH_PROG(MSGFMT, msgfmt, none) + AC_PATH_PROG(MSGMERGE, msgmerge, none) + AC_PATH_PROG(XGETTEXT, xgettext, none) + if test "$MSGFMT" != "none"; then + AC_SEARCH_LIBS(bindtextdomain, [intl], [], + [ + enable_nls="no" + ]) + if test "$enable_nls" = "no"; then + # Destroy the cached result so we can test again + unset ac_cv_search_bindtextdomain + # On some systems, libintl needs libiconv to link properly, + # so try again with -liconv. + AC_SEARCH_LIBS(bindtextdomain, [intl], + [ + enable_nls="yes" + # This is here so that -liconv ends up in LIBS + # if it worked with -liconv. + AC_CHECK_LIB(iconv, libiconv_open) + ], + [ + AC_MSG_WARN([bindtextdomain() not found. Disabling NLS.]) + enable_nls="no" + ], -liconv) + fi + if test "$enable_nls" = "yes"; then + AC_DEFINE(ENABLE_NLS, 1, + [Define to 1 if translation of program messages to the user's + native language is requested.]) + USE_NLS="yes" + fi + fi +fi + +AH_BOTTOM([ +/* Indicate to translators that string X should be translated. Do not look + up the translation at run time; just expand to X. This macro is suitable + for use where a constant string is required at compile time. */ +#define N_(x) x +/* Indicate to translators that we have decided the string X should not be + translated. Expand to X. */ +#define U_(x) x +#ifdef ENABLE_NLS +#include +#include +/* Indicate to translators that string X should be translated. At run time, + look up and return the translation of X. */ +#define _(x) dgettext(PACKAGE_NAME, x) +/* Indicate to translators that strings X1 and X2 are singular and plural + forms of the same message, and should be translated. At run time, return + an appropriate translation depending on the number N. */ +#define Q_(x1, x2, n) dngettext(PACKAGE_NAME, x1, x2, n) +#else +#define _(x) (x) +#define Q_(x1, x2, n) (((n) == 1) ? x1 : x2) +#define gettext(x) (x) +#define dgettext(domain, x) (x) +#endif +]) + +dnl Used to simulate makefile conditionals. +GETTEXT_CODESET=\# +NO_GETTEXT_CODESET=\# +if test $USE_NLS = "yes"; then + AC_CHECK_FUNCS(bind_textdomain_codeset, + [ GETTEXT_CODESET="" ], + [ NO_GETTEXT_CODESET="" ]) +fi +AC_SUBST(GETTEXT_CODESET) +AC_SUBST(NO_GETTEXT_CODESET) + +# Check if we are using GNU gettext. +GNU_GETTEXT=no +MSGFMTFLAGS='' +if test $USE_NLS = "yes"; then + AC_MSG_CHECKING(if we are using GNU gettext) + if $MSGFMT --version 2>&1 | $EGREP GNU > /dev/null; then + GNU_GETTEXT=yes + MSGFMTFLAGS='-c' + fi + AC_MSG_RESULT($GNU_GETTEXT) +fi +AC_SUBST(MSGFMTFLAGS) + +dnl libmagic ------------------- + +libmagic_found=no + +AC_ARG_WITH(libmagic,AS_HELP_STRING([--with-libmagic=PREFIX], + [libmagic filetype detection library]), +[ + if test "$withval" = "yes" ; then + AC_CHECK_HEADER(magic.h, [ + AC_CHECK_LIB(magic, magic_open, [libmagic_found="builtin"]) + ]) + libmagic_prefix="the default locations" + elif test "$withval" != "no"; then + libmagic_prefix=$withval + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS -I$libmagic_prefix/include" + AC_CHECK_HEADERS(magic.h,[ + save_ldflags="$LDFLAGS" + LDFLAGS="-L$libmagic_prefix/lib" + AC_CHECK_LIB(magic, magic_open, [libmagic_found="yes"]) + LDFLAGS="$save_ldflags" + ]) + CPPFLAGS="$save_cppflags" + fi + if test "$withval" != "no" && test "$libmagic_found" = "no"; then + AC_MSG_ERROR([[--with-libmagic requested, but libmagic not found at $libmagic_prefix]]) + fi +], +[ + AC_CHECK_HEADER(magic.h, [ + AC_CHECK_LIB(magic, magic_open, [libmagic_found="builtin"]) + ]) +]) + +if test "$libmagic_found" != "no"; then + AC_DEFINE([SVN_HAVE_LIBMAGIC], [1], [Defined if libmagic support is enabled]) + SVN_MAGIC_LIBS="-lmagic" +fi + +if test "$libmagic_found" = "yes"; then + SVN_MAGIC_INCLUDES="-I$libmagic_prefix/include" + LDFLAGS="$LDFLAGS `SVN_REMOVE_STANDARD_LIB_DIRS(-L$libmagic_prefix/lib)`" +fi + +AC_SUBST(SVN_MAGIC_INCLUDES) +AC_SUBST(SVN_MAGIC_LIBS) + +dnl KWallet ------------------- +SVN_LIB_KWALLET + +if test "$svn_lib_kwallet" = "yes"; then + AC_DEFINE([SVN_HAVE_KWALLET], 1, + [Defined if KWallet support is enabled]) +fi + +dnl plaintext passwords ------------------- +AC_ARG_ENABLE(plaintext-password-storage, +AS_HELP_STRING([--disable-plaintext-password-storage], + [Disable on-disk caching of plaintext passwords and passphrases. + (Leaving this functionality enabled will not force Subversion + to store passwords in plaintext, but does permit users to + explicitly allow that behavior via runtime configuration.)]), +[ + if test "$enableval" = "no"; then + AC_MSG_NOTICE([Disabling plaintext password/passphrase storage]) + AC_DEFINE(SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE, 1, + [Defined if plaintext password/passphrase storage is disabled]) + fi +]) + +dnl Build and install rules ------------------- + +INSTALL_STATIC_RULES="install-bin install-docs" +INSTALL_RULES="install-fsmod-lib install-ramod-lib install-lib install-include install-static" +INSTALL_RULES="$INSTALL_RULES $INSTALL_APACHE_RULE" +BUILD_RULES="fsmod-lib ramod-lib lib bin test sub-test $BUILD_APACHE_RULE tools" + +if test "$svn_lib_berkeley_db" = "yes"; then + BUILD_RULES="$BUILD_RULES bdb-lib bdb-test" + INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-fsmod-lib/install-fsmod-lib install-bdb-lib/'`" + INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-bdb-lib" + BDB_TEST_DEPS="\$(BDB_TEST_DEPS)" + BDB_TEST_PROGRAMS="\$(BDB_TEST_PROGRAMS)" +fi + +if test "$svn_lib_serf" = "yes"; then + BUILD_RULES="$BUILD_RULES serf-lib" + INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-ramod-lib/install-ramod-lib install-serf-lib/'`" + INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-serf-lib" +fi + +if test "$svn_lib_kwallet" = "yes"; then + BUILD_RULES="$BUILD_RULES kwallet-lib" + INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-kwallet-lib/'`" + INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-kwallet-lib" +fi + +if test "$found_gnome_keyring" = "yes"; then + BUILD_RULES="$BUILD_RULES gnome-keyring-lib" + INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-gnome-keyring-lib/'`" + INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-gnome-keyring-lib" +fi + +if test "$USE_NLS" = "yes"; then + BUILD_RULES="$BUILD_RULES locale" + INSTALL_RULES="$INSTALL_RULES install-locale" +fi + +AC_SUBST(BUILD_RULES) +AC_SUBST(INSTALL_STATIC_RULES) +AC_SUBST(INSTALL_RULES) +AC_SUBST(BDB_TEST_DEPS) +AC_SUBST(BDB_TEST_PROGRAMS) + +dnl Check for header files ---------------- + +dnl Standard C headers +AC_HEADER_STDC + +dnl Check for typedefs, structures, and compiler characteristics ---------- + +dnl if compiler doesn't understand `const', then define it empty +AC_C_CONST + +dnl if non-existent, define size_t to be `unsigned' +AC_TYPE_SIZE_T + + +dnl Check for library functions ---------- + +AC_FUNC_MEMCMP + +dnl svn_error's default warning handler uses vfprintf() +AC_FUNC_VPRINTF + +dnl check for functions needed in special file handling +AC_CHECK_FUNCS(symlink readlink) + +dnl check for uname +AC_CHECK_HEADERS(sys/utsname.h, [AC_CHECK_FUNCS(uname)], []) + +dnl check for termios +AC_CHECK_HEADER(termios.h,[ + AC_CHECK_FUNCS(tcgetattr tcsetattr,[ + AC_DEFINE(HAVE_TERMIOS_H,1,[Defined if we have a usable termios library.]) + ]) +]) + +dnl Process some configuration options ---------- + +AC_ARG_WITH(openssl, +AS_HELP_STRING([--with-openssl], + [This option does NOT affect the Subversion build process in any + way. It tells an integrated Serf HTTP client library build + process where to locate the OpenSSL library when (and only when) + building Serf as an integrated part of the Subversion build + process. When linking to a previously installed version of Serf + instead, you do not need to use this option.]), +[]) + +AC_ARG_ENABLE(debug, +AS_HELP_STRING([--enable-debug], + [Turn on debugging]), +[ + if test "$enableval" = "yes" ; then + enable_debugging="yes" + else + enable_debugging="no" + fi +], +[ + # Neither --enable-debug nor --disable-debug was passed. + enable_debugging="maybe" +]) + +AC_ARG_ENABLE(optimize, +AS_HELP_STRING([--enable-optimize], + [Turn on optimizations]), +[ + if test "$enableval" = "yes" ; then + enable_optimization="yes" + else + enable_optimization="no" + fi +], +[ + # Neither --enable-optimize nor --disable-optimize was passed. + enable_optimization="maybe" +]) + +dnl Use -Wl,--no-undefined during linking of some libraries +AC_ARG_ENABLE(disallowing-of-undefined-references, + [AS_HELP_STRING([--enable-disallowing-of-undefined-references], + [Use -Wl,--no-undefined flag during linking of some libraries to disallow undefined references])]) +if test "$enable_disallowing_of_undefined_references" != "yes" && test "`uname`" != "Linux"; then + enable_disallowing_of_undefined_references="no" +fi +if test "$enable_disallowing_of_undefined_references" != "no"; then + AC_MSG_CHECKING([for -Wl,--no-undefined]) + old_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -Wl,--no-undefined" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(){;}]])], [svn_wl_no_undefined="yes"], [svn_wl_no_undefined="no"]) + LDFLAGS="$old_LDFLAGS" + if test "$svn_wl_no_undefined" = "yes"; then + AC_MSG_RESULT([yes]) + for library_dir in "$abs_srcdir/subversion/libsvn_"*; do + eval "`basename $library_dir`_LDFLAGS=-Wl,--no-undefined" + done + else + AC_MSG_RESULT([no]) + if test "$enable_disallowing_of_undefined_references" = "yes"; then + AC_MSG_ERROR([--enable-disallowing-of-undefined-references explicitly requested, but -Wl,--no-undefined not supported]) + fi + fi +fi +AC_SUBST([libsvn_auth_gnome_keyring_LDFLAGS]) +AC_SUBST([libsvn_auth_kwallet_LDFLAGS]) +AC_SUBST([libsvn_client_LDFLAGS]) +AC_SUBST([libsvn_delta_LDFLAGS]) +AC_SUBST([libsvn_diff_LDFLAGS]) +AC_SUBST([libsvn_fs_LDFLAGS]) +AC_SUBST([libsvn_fs_base_LDFLAGS]) +AC_SUBST([libsvn_fs_fs_LDFLAGS]) +AC_SUBST([libsvn_fs_util_LDFLAGS]) +AC_SUBST([libsvn_ra_LDFLAGS]) +AC_SUBST([libsvn_ra_local_LDFLAGS]) +AC_SUBST([libsvn_ra_serf_LDFLAGS]) +AC_SUBST([libsvn_ra_svn_LDFLAGS]) +AC_SUBST([libsvn_repos_LDFLAGS]) +AC_SUBST([libsvn_subr_LDFLAGS]) +AC_SUBST([libsvn_wc_LDFLAGS]) + + +AC_ARG_ENABLE(maintainer-mode, +AS_HELP_STRING([--enable-maintainer-mode], + [Turn on debugging and very strict compile-time warnings]), +[ + if test "$enableval" = "yes" ; then + if test "$enable_debugging" = "no" ; then + AC_MSG_ERROR([Can't have --disable-debug and --enable-maintainer-mode]) + fi + enable_debugging=yes + + dnl Enable some extra warnings. Put these before the user's flags + dnl so the user can specify flags that override these. + if test "$GCC" = "yes"; then + AC_MSG_NOTICE([maintainer-mode: adding GCC warning flags]) + + dnl some additional flags that can be handy for an occasional review, + dnl but throw too many warnings in svn code, of too little importance, + dnl to keep these enabled. Remove the "dnl" to do a run with these + dnl switches enabled. + dnl ./configure CUSERFLAGS="-Wswitch-enum -Wswitch-default" + + dnl Add each of the following flags only if the C compiler accepts it. + CFLAGS_KEEP="$CFLAGS" + CFLAGS="" + + SVN_CFLAGS_ADD_IFELSE([-Werror=implicit-function-declaration]) + SVN_CFLAGS_ADD_IFELSE([-Werror=declaration-after-statement]) + SVN_CFLAGS_ADD_IFELSE([-Wextra-tokens]) + SVN_CFLAGS_ADD_IFELSE([-Wnewline-eof]) + SVN_CFLAGS_ADD_IFELSE([-Wshorten-64-to-32]) + SVN_CFLAGS_ADD_IFELSE([-Wold-style-definition]) + SVN_CFLAGS_ADD_IFELSE([-Wno-system-headers]) + SVN_CFLAGS_ADD_IFELSE([-Wno-format-nonliteral]) + + CMAINTAINERFLAGS="$CFLAGS $CMAINTAINERFLAGS" + CFLAGS="$CFLAGS_KEEP" + + dnl Add flags that all versions of GCC (should) support + CMAINTAINERFLAGS="-Wall -Wpointer-arith -Wwrite-strings -Wshadow -Wformat=2 -Wunused -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wno-multichar -Wredundant-decls -Wnested-externs -Winline -Wno-long-long $CMAINTAINERFLAGS" + fi + if test "$GXX" = "yes"; then + AC_MSG_NOTICE([maintainer-mode: adding G++ warning flags]) + + dnl Add each of the following flags only if the C++ compiler accepts it. + CXXFLAGS_KEEP="$CXXFLAGS" + CXXFLAGS="" + + SVN_CXXFLAGS_ADD_IFELSE([-Wextra-tokens]) + SVN_CXXFLAGS_ADD_IFELSE([-Wnewline-eof]) + SVN_CXXFLAGS_ADD_IFELSE([-Wshorten-64-to-32]) + SVN_CXXFLAGS_ADD_IFELSE([-Wno-system-headers]) + + CXXMAINTAINERFLAGS="$CXXFLAGS $CXXMAINTAINERFLAGS" + CXXFLAGS="$CXXFLAGS_KEEP" + + dnl Add flags that all versions of G++ (should) support + CXXMAINTAINERFLAGS="-Wall -Wpointer-arith -Wwrite-strings -Wshadow -Wunused -Wunreachable-code $CXXMAINTAINERFLAGS" + fi + fi +]) + +if test "$enable_debugging" = "yes" ; then + dnl At the moment, we don't want optimization, because we're + dnl debugging. Unless optiization was explicitly enabled. + if test "$enable_optimization" != "yes"; then + AC_MSG_NOTICE([Disabling optimizations for debugging]) + CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"] + CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"] + fi + dnl Add debugging flags, unless they were set by the user + if test -z ["`echo $CUSERFLAGS' ' | $EGREP -- '-g[0-9]? '`"]; then + AC_MSG_NOTICE([Enabling debugging for C]) + CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"] + SVN_CFLAGS_ADD_IFELSE([-fno-inline]) + SVN_CFLAGS_ADD_IFELSE([-fno-omit-frame-pointer]) + SVN_CFLAGS_ADD_IFELSE([-g3],[],[ + SVN_CFLAGS_ADD_IFELSE([-g2],[],[ + SVN_CFLAGS_ADD_IFELSE([-g])])]) + fi + if test -z ["`echo $CXXUSERFLAGS' ' | $EGREP -- '-g[0-9]? '`"]; then + AC_MSG_NOTICE([Enabling debugging for C++]) + CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"] + SVN_CXXFLAGS_ADD_IFELSE([-fno-inline]) + SVN_CXXFLAGS_ADD_IFELSE([-fno-omit-frame-pointer]) + SVN_CXXFLAGS_ADD_IFELSE([-g3],[],[ + SVN_CXXFLAGS_ADD_IFELSE([-g2],[],[ + SVN_CXXFLAGS_ADD_IFELSE([-g])])]) + fi + dnl SVN_DEBUG enables specific features for developer builds + dnl AP_DEBUG enables specific (Apache) features for developer builds + CFLAGS="$CFLAGS -DSVN_DEBUG -DAP_DEBUG" + CXXFLAGS="$CXXFLAGS -DSVN_DEBUG -DAP_DEBUG" +elif test "$enable_debugging" = "no" ; then + AC_MSG_NOTICE([Disabling debugging]) + CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"] + CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"] + dnl Compile with NDEBUG to get rid of assertions + CFLAGS="$CFLAGS -DNDEBUG" + CXXFLAGS="$CXXFLAGS -DNDEBUG" +# elif test "$enable_debugging" = "maybe" ; then +# # do nothing +fi + +if test "$enable_optimization" = "yes"; then + dnl Add optimization flags, unless they were set by the user + if test -z ["`echo $CUSERFLAGS' ' | $EGREP -- '-O[^ ]* '`"]; then + CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"] + if test "$enable_debugging" = "yes"; then + AC_MSG_NOTICE([Enabling optimizations for C (with debugging enabled)]) + SVN_CFLAGS_ADD_IFELSE([-O1],[],[ + SVN_CFLAGS_ADD_IFELSE([-O])]) + else + AC_MSG_NOTICE([Enabling optimizations for C]) + SVN_CFLAGS_ADD_IFELSE([-O3],[],[ + SVN_CFLAGS_ADD_IFELSE([-O2],[],[ + SVN_CFLAGS_ADD_IFELSE([-O1],[],[ + SVN_CFLAGS_ADD_IFELSE([-O])])])]) + SVN_CFLAGS_ADD_IFELSE([-Wno-clobbered]) + SVN_CFLAGS_ADD_IFELSE([-flto]) + SVN_CFLAGS_ADD_IFELSE([-fwhole-program]) + fi + fi + if test -z ["`echo $CXXUSERFLAGS' ' | $EGREP -- '-O[^ ]* '`"]; then + CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"] + if test "$enable_debugging" = "yes"; then + AC_MSG_NOTICE([Enabling optimizations for C++ (with debugging enabled)]) + SVN_CXXFLAGS_ADD_IFELSE([-O1],[],[ + SVN_CXXFLAGS_ADD_IFELSE([-O])]) + else + AC_MSG_NOTICE([Enabling optimizations for C++]) + SVN_CXXFLAGS_ADD_IFELSE([-O3],[],[ + SVN_CXXFLAGS_ADD_IFELSE([-O2],[],[ + SVN_CXXFLAGS_ADD_IFELSE([-O1],[],[ + SVN_CXXFLAGS_ADD_IFELSE([-O])])])]) + SVN_CXXFLAGS_ADD_IFELSE([-Wno-clobbered]) + SVN_CXXFLAGS_ADD_IFELSE([-flto]) + SVN_CXXFLAGS_ADD_IFELSE([-fwhole-program]) + fi + fi +elif test "$enable_optimization" = "no"; then + dnl Remove all optimization flags + AC_MSG_NOTICE([Disabling optimizations]) + CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"] + CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"] +# elif test "$enable_optimization" = "maybe" ; then +# # do nothing +fi + + +AC_ARG_ENABLE(full-version-match, +AS_HELP_STRING([--disable-full-version-match], + [Disable the full version match rules when checking + Subversion library compatibility.]), +[ + if test "$enableval" = "no" ; then + AC_MSG_NOTICE([Disabling svn full version matching]) + AC_DEFINE(SVN_DISABLE_FULL_VERSION_MATCH, 1, + [Defined if the full version matching rules are disabled]) + fi +]) + +AC_ARG_WITH(editor, +AS_HELP_STRING([--with-editor=PATH], + [Specify a default editor for the subversion client.]), +[ + + if test "$withval" = "yes" ; then + AC_MSG_ERROR([--with-editor requires an argument.]) + else + SVN_CLIENT_EDITOR=$withval + AC_DEFINE_UNQUOTED(SVN_CLIENT_EDITOR, "$SVN_CLIENT_EDITOR", + [The path of a default editor for the client.]) + + fi + +]) + +SVN_LIB_Z + +MOD_ACTIVATION="" +AC_ARG_ENABLE(mod-activation, +AS_HELP_STRING([--enable-mod-activation], + [Enable mod_dav_svn in httpd.conf]), +[ + if test "$enableval" = "yes" ; then + MOD_ACTIVATION="-a" + AC_MSG_NOTICE([Enabling apache module activation]) + else + AC_MSG_NOTICE([Disabling apache module activation]) + fi +]) +AC_SUBST(MOD_ACTIVATION) + + + +AC_ARG_ENABLE(gcov, +AC_HELP_STRING([--enable-gcov], + [Turn on gcov coverage testing (GCC only).]), +[ + if test "$enableval" = "yes" ; then + dnl Probably other compilers support something similar; + dnl feel free to extend this to include them. + if test "$GCC" = "yes"; then + if test "$svn_enable_shared" = "yes" ; then + AC_MSG_ERROR([Can't have --enable-gcov without --disable-shared (we + recommend also using --enable-all-static).]) + fi + if test ! "$enable_all_static" = "yes" ; then + AC_MSG_WARN(We recommend --enable-all-static with --enable-gcov.) + fi + AC_MSG_NOTICE([Enabling gcov coverage testing.]) + CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage" + CXXFLAGS="$CXXFLAGS -fprofile-arcs -ftest-coverage" + else + AC_MSG_ERROR([We only support --enable-gcov with GCC right now.]) + fi + fi +]) + +AC_ARG_ENABLE(gprof, +AS_HELP_STRING([--enable-gprof], + [Produce gprof profiling data in 'gmon.out' (GCC only).]), +[ + if test "$enableval" = "yes" ; then + dnl Probably other compilers support -pg or something similar; + dnl feel free to extend this to include them. + if test "$GCC" = "yes"; then + if test "$svn_enable_shared" = "yes" ; then + AC_MSG_ERROR([Can't have --enable-gprof without --disable-shared (we + recommend also using --enable-all-static).]) + fi + if test ! "$enable_all_static" = "yes" ; then + AC_MSG_WARN(We recommend --enable-all-static with --enable-gprof.) + fi + AC_MSG_NOTICE([Enabling gprof profiling data (to gmon.out).]) + CFLAGS="$CFLAGS -pg" + CXXFLAGS="$CXXFLAGS -pg" + LT_LDFLAGS="$LT_LDFLAGS -pg" + else + AC_MSG_ERROR([We only support --enable-gprof with GCC right now.]) + fi + fi +]) + + +# Scripting and Bindings languages + +# Python: Used for testsuite, and bindings + + +PYTHON="`$abs_srcdir/build/find_python.sh`" +if test -z "$PYTHON"; then + AC_MSG_WARN([Python 2.5 or later is required to run the testsuite]) + AC_MSG_WARN([or to use the Subversion Python bindings]) + AC_MSG_WARN([]) + AC_MSG_WARN([If you have a suitable Python installed, but not on the]) + AC_MSG_WARN([PATH, set the environment variable PYTHON to the full path]) + AC_MSG_WARN([to the Python executable, and re-run configure]) +fi +AC_PATH_PROGS(PYTHON, "$PYTHON", none) + +# The minimum version for the JVM runtime for our Java bytecode. +JAVA_OLDEST_WORKING_VER='1.5' +# SVN_CHECK_JDK sets $JAVA_CLASSPATH +SVN_CHECK_JDK($JAVA_OLDEST_WORKING_VER) + +AC_PATH_PROG(PERL, perl, none) + +if test -n "$RUBY"; then + AC_PATH_PROG(RUBY, "$RUBY", none) +else + AC_PATH_PROGS(RUBY, ruby ruby1.8 ruby18 ruby1.9 ruby1 ruby1.9.3 ruby193, none) +fi +if test "$RUBY" != "none"; then + AC_MSG_CHECKING([rb_hash_foreach]) + if "$RUBY" -r mkmf -e 'exit(have_func("rb_hash_foreach") ? 0 : 1)' >/dev/null; then + AC_MSG_RESULT([yes]) + if test -n "$RDOC"; then + AC_PATH_PROG(RDOC, "$RDOC", none) + else + AC_PATH_PROGS(RDOC, rdoc rdoc1.8 rdoc18 rdoc1.9 rdoc19 rdoc1.9.3 rdoc193, none) + fi + AC_CACHE_CHECK([for Ruby major version], [svn_cv_ruby_major],[ + svn_cv_ruby_major="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MAJOR))'`" + ]) + RUBY_MAJOR="$svn_cv_ruby_major" + + AC_CACHE_CHECK([for Ruby minor version], [svn_cv_ruby_minor],[ + svn_cv_ruby_minor="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MINOR))'`" + ]) + RUBY_MINOR="$svn_cv_ruby_minor" + + AC_CACHE_CHECK([for Ruby teeny version], [svn_cv_ruby_teeny],[ + svn_cv_ruby_teeny="`$RUBY -rrbconfig -e 'major, minor, teeny = RUBY_VERSION.split("."); print teeny;'`" + ]) + RUBY_TEENY="$svn_cv_ruby_teeny" + + AC_SUBST(RUBY_MAJOR) + AC_SUBST(RUBY_MINOR) + AC_SUBST(RUBY_TEENY) + if test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -gt "8" -a "$RUBY_TEENY" -lt "3" \); then + # Disallow Ruby between 1.8.7 and 1.9.3 + RUBY="none" + AC_MSG_WARN([The detected Ruby is between 1.9 and 1.9.3]) + AC_MSG_WARN([Only 1.8.x and 1.9.3 releases are supported at this time]) + elif test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -eq "9" -a "$RUBY_TEENY" -eq "3" \); then + #Warn about 1.9.3 support + AC_MSG_WARN([WARNING: The detected Ruby is 1.9.3]) + AC_MSG_WARN([WARNING: Only 1.8.x releases are fully supported, 1.9.3 support is new]) + fi + else + AC_MSG_RESULT([no]) + RUBY="none" + AC_MSG_WARN([The detected Ruby is too old for Subversion to use]) + AC_MSG_WARN([A Ruby which has rb_hash_foreach is required to use the]) + AC_MSG_WARN([Subversion Ruby bindings]) + AC_MSG_WARN([Upgrade to the official 1.8.2 release, or later]) + fi +fi + +SVN_CHECK_SWIG + +SVN_CHECK_CTYPESGEN + +dnl decide whether we want to link against the RA/FS libraries +AC_ARG_ENABLE(runtime-module-search, +AS_HELP_STRING([--enable-runtime-module-search], + [Turn on dynamic loading of RA/FS libraries including + third-party FS libraries]), +[ + if test "$enableval" = "yes"; then + use_dso=yes + if test "$svn_enable_shared" = "no"; then + AC_MSG_ERROR([--enable-runtime-module-search conflicts with --disable-shared]) + fi + AC_DEFINE(SVN_USE_DSO, 1, + [Defined if svn should try to load DSOs]) + fi +]) + +if test "$svn_enable_shared" = "no" || test "$use_dso" != "yes"; then + AC_DEFINE(SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL, 1, + [Defined if libsvn_client should link against libsvn_ra_local]) + svn_ra_lib_deps="\$(RA_LOCAL_DEPS)" + svn_ra_lib_install_deps="install-ramod-lib" + svn_ra_lib_link="\$(RA_LOCAL_LINK)" + + AC_DEFINE(SVN_LIBSVN_CLIENT_LINKS_RA_SVN, 1, + [Defined if libsvn_client should link against libsvn_ra_svn]) + svn_ra_lib_deps="$svn_ra_lib_deps \$(RA_SVN_DEPS)" + svn_ra_lib_link="$svn_ra_lib_link \$(RA_SVN_LINK)" + + if test "$svn_lib_serf" = "yes"; then + AC_DEFINE(SVN_LIBSVN_CLIENT_LINKS_RA_SERF, 1, + [Defined if libsvn_client should link against libsvn_ra_serf]) + svn_ra_lib_deps="$svn_ra_lib_deps \$(RA_SERF_DEPS)" + svn_ra_lib_install_deps="$svn_ra_lib_install_deps install-serf-lib" + svn_ra_lib_link="$svn_ra_lib_link \$(RA_SERF_LINK)" + fi + + SVN_RA_LIB_DEPS=$svn_ra_lib_deps + SVN_RA_LIB_INSTALL_DEPS=$svn_ra_lib_install_deps + SVN_RA_LIB_LINK=$svn_ra_lib_link + + AC_DEFINE(SVN_LIBSVN_FS_LINKS_FS_FS, 1, + [Defined if libsvn_fs should link against libsvn_fs_fs]) + svn_fs_lib_deps="\$(FS_FS_DEPS)" + svn_fs_lib_install_deps="install-fsmod-lib" + svn_fs_lib_link="\$(FS_FS_LINK)" + + if test "$svn_lib_berkeley_db" = "yes"; then + AC_DEFINE(SVN_LIBSVN_FS_LINKS_FS_BASE, 1, + [Defined if libsvn_fs should link against libsvn_fs_base]) + svn_fs_lib_deps="$svn_fs_lib_deps \$(FS_BASE_DEPS)" + svn_fs_lib_install_deps="$svn_fs_lib_install_deps install-bdb-lib" + svn_fs_lib_link="$svn_fs_lib_link \$(FS_BASE_LINK)" + fi + + SVN_FS_LIB_DEPS=$svn_fs_lib_deps + SVN_FS_LIB_INSTALL_DEPS=$svn_fs_lib_install_deps + SVN_FS_LIB_LINK=$svn_fs_lib_link +fi + +AC_SUBST(SVN_RA_LIB_DEPS) +AC_SUBST(SVN_RA_LIB_INSTALL_DEPS) +AC_SUBST(SVN_RA_LIB_LINK) +AC_SUBST(SVN_FS_LIB_DEPS) +AC_SUBST(SVN_FS_LIB_INSTALL_DEPS) +AC_SUBST(SVN_FS_LIB_LINK) + +# ==== JavaHL ================================================================ + +dnl Possibly compile JavaHL +do_javahl_build=no +AC_ARG_ENABLE(javahl, + AS_HELP_STRING([--enable-javahl], + [Enable compilation of Java high-level bindings (requires C++)]), + [ if test "$enableval" = "yes" ; then + do_javahl_build="yes" + fi + ]) + +JAVAHL_OBJDIR="" +INSTALL_EXTRA_JAVAHL_LIB="" +FIX_JAVAHL_LIB="" +JAVAHL_TESTS_TARGET="" +JAVAHL_COMPAT_TESTS_TARGET="" +LT_CXX_LIBADD="" +if test "$do_javahl_build" = "yes"; then + dnl Check for suitable JDK + if test "$JDK_SUITABLE" = "no"; then + AC_MSG_ERROR([Cannot compile JavaHL without a suitable JDK. + Please specify a suitable JDK using the --with-jdk option.]) + fi + + dnl The temporary directory where libtool compiles libsvnjavahl. + JAVAHL_OBJDIR='$(libsvnjavahl_PATH)/.libs' + + os_arch=`uname` + if test "$os_arch" = "Darwin"; then + dnl On Darwin, JNI libs must be installed as .jnilib + INSTALL_EXTRA_JAVAHL_LIB='ln -sf $(libdir)/libsvnjavahl-1.dylib $(libdir)/libsvnjavahl-1.jnilib' + FIX_JAVAHL_LIB="ln -sf libsvnjavahl-1.dylib $JAVAHL_OBJDIR/libsvnjavahl-1.jnilib" + fi + # This segment (and the rest of r10800) is very likely unnecessary + # with libtool 1.5, which automatically adds libstdc++ as a + # dependency to the C++ libraries it builds. So at some future time + # when autogen.sh requires libtool 1.5 or higher, we can get rid of + # it. + AC_MSG_CHECKING([for additional flags to link C++ libraries]) + if test "x$ac_compiler_gnu" = "xyes"; then + LT_CXX_LIBADD="-lstdc++" + AC_MSG_RESULT([$LT_CXX_LIBADD]) + else + AC_MSG_RESULT([none needed]) + fi +fi +AC_SUBST(INSTALL_EXTRA_JAVAHL_LIB) +AC_SUBST(JAVAHL_OBJDIR) +AC_SUBST(FIX_JAVAHL_LIB) +AC_SUBST(LT_CXX_LIBADD) + +AC_ARG_WITH(junit, +AS_HELP_STRING([--with-junit=PATH], + [Specify a path to the junit JAR file.]), +[ + if test "$withval" != "no"; then + if test -n "$JAVA_CLASSPATH"; then + JAVA_CLASSPATH="$withval:$JAVA_CLASSPATH" + else + JAVA_CLASSPATH="$withval" + fi + JAVAHL_TESTS_TARGET="javahl-tests" + JAVAHL_COMPAT_TESTS_TARGET="javahl-compat-tests" + fi +]) +AC_SUBST(JAVA_CLASSPATH) +AC_SUBST(JAVAHL_TESTS_TARGET) +AC_SUBST(JAVAHL_COMPAT_TESTS_TARGET) + +# ==== Miscellaneous bits ==================================================== + +# Strip '-no-cpp-precomp' from CPPFLAGS for the clang compiler +### I think we get this flag from APR, so the fix probably belongs there +if test "$CC" = "clang"; then + SVN_STRIP_FLAG(CPPFLAGS, [-no-cpp-precomp ]) +fi + +dnl Since this is used only on Unix-y systems, define the path separator as '/' +AC_DEFINE_UNQUOTED(SVN_PATH_LOCAL_SEPARATOR, '/', + [Defined to be the path separator used on your local filesystem]) + +AC_DEFINE_UNQUOTED(SVN_NULL_DEVICE_NAME, "/dev/null", + [Defined to be the null device for the system]) + +DEFAULT_FS_TYPE="fsfs" +AC_DEFINE_UNQUOTED(DEFAULT_FS_TYPE, "$DEFAULT_FS_TYPE", + [The fs type to use by default]) + +DEFAULT_HTTP_LIBRARY="serf" +AC_DEFINE_UNQUOTED(DEFAULT_HTTP_LIBRARY, "$DEFAULT_HTTP_LIBRARY", + [The http library to use by default]) + +# BSD/OS (BSDi) needs to use a different include syntax in Makefile +INCLUDE_OUTPUTS="include \$(top_srcdir)/build-outputs.mk" +case "$host" in + *bsdi*) + # Check whether they've installed GNU make + if ! make --version > /dev/null 2>&1; then + # BSDi make + INCLUDE_OUTPUTS=".include \"\$(top_srcdir)/build-outputs.mk\"" + fi + ;; +esac +AC_SUBST(INCLUDE_OUTPUTS) + +# ==== Detection complete - output and run config.status ===================== + +AC_CONFIG_HEADERS(subversion/svn_private_config.h.tmp:subversion/svn_private_config.h.in) +AC_CONFIG_COMMANDS([svn_private_config.h.tmp], + [svn_cf=subversion/svn_private_config.h; + $SED -e "s/@SVN_DB_HEADER@/$SVN_DB_HEADER/" $svn_cf.tmp > $svn_cf.tmp.new + cmp -s $svn_cf.tmp.new $svn_cf || mv -f $svn_cf.tmp.new $svn_cf + rm -f $svn_cf.tmp.new $svn_cf.tmp], + [SED="$SED" + SVN_DB_HEADER="$SVN_DB_HEADER"]) +AC_CONFIG_FILES([Makefile]) + +SVN_CONFIG_SCRIPT(tools/backup/hot-backup.py) +SVN_CONFIG_SCRIPT(tools/hook-scripts/commit-access-control.pl) +SVN_CONFIG_SCRIPT(subversion/bindings/swig/perl/native/Makefile.PL) +if test -e packages/solaris/pkginfo.in; then + SVN_CONFIG_SCRIPT(packages/solaris/pkginfo) +fi +AC_SUBST(SVN_CONFIG_SCRIPT_FILES) + +# Ensure that SWIG is checked after reconfiguration. +rm -f .swig_checked + +dnl Provide ${host} for use in compiled code (for svn --version) +AC_DEFINE_UNQUOTED([SVN_BUILD_HOST], "${host}", + [Defined to the config.guess name of the build system]) + +dnl Provide ${target} for use in compiled code (for user-agent string) +AC_DEFINE_UNQUOTED([SVN_BUILD_TARGET], "${target}", + [Defined to the config.guess name of the build target]) + +AC_OUTPUT + +# ==== Print final messages to user ========================================== + +dnl Configure is long - users tend to miss warnings printed during it. +dnl Hence, print a warnings about what we did and didn't configure at the +dnl end, where people will actually see them. + +if test "$svn_lib_berkeley_db" = "no" && test "$with_berkeley_db" != "no"; then + db_version="$SVN_FS_WANT_DB_MAJOR.$SVN_FS_WANT_DB_MINOR.$SVN_FS_WANT_DB_PATCH" + AC_MSG_WARN([we have configured without BDB filesystem support + + +You don't seem to have Berkeley DB version $db_version or newer +installed and linked to APR-UTIL. We have created a Makefile which will build +Subversion without support for the Berkeley DB back-end. You can find the +latest version of Berkeley DB here: + + http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html + +or explicitly specify --without-berkeley-db to silence this warning. +]) +fi diff --git a/doc/README b/doc/README new file mode 100644 index 000000000000..3710d73f6e83 --- /dev/null +++ b/doc/README @@ -0,0 +1,28 @@ + Documentation for Subversion + ============================ + +A rough guide: + + http://svnbook.red-bean.com/ + "Version Control with Subversion" + (a.k.a. "The Subversion Book", "The Svnbook", + and formerly entitled + "Subversion: The Definitive Guide".) + This is the book that has been published by + O'Reilly & Associates. For both newbies and + experts alike. Written in DocBook Lite, + and now maintained in a separate repository + of its own. + + programmer/ Documents for Subversion programmers. + + programmer/WritingChangeLogs.txt + A longer version of the info in + www/hacking.html. + + user/ Documents for Subversion users. + + user/lj_article.txt An introductory article from Linux Journal. + + user/*.html Some documentation that should probably be + migrated to DocBook Lite and misc-docs/. diff --git a/doc/doxygen.conf b/doc/doxygen.conf new file mode 100644 index 000000000000..6c17d1be4be7 --- /dev/null +++ b/doc/doxygen.conf @@ -0,0 +1,1522 @@ +# Doxyfile 1.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = Subversion + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/doxygen + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = "copyright=@if copyrights " \ + endcopyright=@endif + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text " + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = subversion/include \ + subversion/include/private/svn_doxygen.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = svn_ SVN_ + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 1 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) +# there is already a search function so this one should typically +# be disabled. + +SEARCHENGINE = YES + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = DOXYGEN \ + DOXYGEN_SHOULD_SKIP_THIS \ + __attribute__(x)= \ + AP_MODULE_DECLARE(x)=x \ + "SVN_ERROR_START=typedef enum svn_errno_t { SVN_WARNING = APR_OS_START_USERERR + 1, " \ + "SVN_ERRDEF(num,offset,str)=/** str */ num = offset, " \ + "SVN_ERROR_END=SVN_ERR_LAST } svn_errno_t; " + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/doc/programmer/WritingChangeLogs.txt b/doc/programmer/WritingChangeLogs.txt new file mode 100644 index 000000000000..c5f50cd0310c --- /dev/null +++ b/doc/programmer/WritingChangeLogs.txt @@ -0,0 +1,220 @@ +This is an essay by Jim Blandy on maintaining +ChangeLog entries. + +Although Subversion generates its ChangeLogs from svn log data, +instead of keeping independent ChangeLog files, most of the advice +below is as applicable to cvs log messages as to ChangeLog entries. + + +Maintaining the ChangeLog +========================= + +A project's ChangeLog provides a history of development. Comments in +the code should explain the code's present state, but ChangeLog +entries should explain how and when it got that way. The ChangeLog +must show: + +* the relative order in which changes entered the code, so you can + see the context in which a change was made, and + +* the date at which the change entered the code, so you can relate the + change to outside events, like branch cuts, code freezes, and + releases. + +In the case of CVS, these refer to when the change was committed, +because that is the context in which other developers will see the +change. + +Every change to the sources should have a ChangeLog entry. The value +of the ChangeLog becomes much less if developers cannot rely on its +completeness. Even if you've only changed comments, write an entry +that says, "Doc fix." The only changes you needn't log are small +changes that have no effect on the source, like formatting tweaks. + +In order to keep the ChangeLog a manageable size, at the beginning of +each year, the ChangeLog should be renamed to "ChangeLog-YYYY", and a +fresh ChangeLog file started. + + +How to write ChangeLog entries +------------------------------ + +ChangeLog entries should be full sentences, not sentence fragments. +Fragments are more often ambiguous, and it takes only a few more +seconds to write out what you mean. Fragments like `New file' or `New +function' are acceptable, because they are standard idioms, and all +further details should appear in the source code. + +The log entry should mention every file changed. It should also +mention by name every function, variable, macro, makefile target, +grammar rule, etc. you changed. However, there are common-sense +exceptions: + +* If you have made a change which requires trivial changes throughout + the rest of the program (e.g., renaming a variable), you needn't + name all the functions affected. + +* If you have rewritten a file completely, the reader understands that + everything in it has changed, so your log entry may simply give the + file name, and say "Rewritten". + +In general, there is a tension between making entries easy to find by +searching for identifiers, and wasting time or producing unreadable +entries by being exhaustive. Use your best judgement --- and be +considerate of your fellow developers. + +Group ChangeLog entries into "paragraphs", separated by blank lines. +Each paragraph should be a set of changes that accomplish a single +goal. Independent changes should be in separate paragraphs. For +example: + + 1999-03-24 Stan Shebs + + * configure.host (mips-dec-mach3*): Use mipsm3, not mach3. + + Attempt to sort out SCO-related configs. + * configure.host (i[3456]86-*-sysv4.2*): Use this instead of + i[3456]86-*-sysv4.2MP and i[3456]86-*-sysv4.2uw2*. + (i[3456]86-*-sysv5*): Recognize this. + * configure.tgt (i[3456]86-*-sco3.2v5*, i[3456]86-*-sco3.2v4*): + Recognize these. + +Even though this entry describes two changes to `configure.host', +they're in separate paragraphs, because they're unrelated changes. +The second change to `configure.host' is grouped with another change +to `configure.tgt', because they both serve the same purpose. + +Also note that the author has kindly recorded his overall motivation +for the paragraph, so we don't have to glean it from the individual +changes. + +The header line for the ChangeLog entry should have the format shown +above. If you are using an old version of Emacs (before 20.1) that +generates entries with more verbose dates, consider using +`etc/add-log.el', from the GDB source tree. If you are using vi, +consider using the macro in `etc/add-log.vi'. Both of these generate +entries in the newer, terser format. + +One should never need the ChangeLog to understand the current code. +If you find yourself writing a significant explanation in the +ChangeLog, you should consider carefully whether your text doesn't +actually belong in a comment, alongside the code it explains. Here's +an example of doing it right: + + 1999-02-23 Tom Tromey + + * cplus-dem.c (consume_count): If `count' is unreasonable, + return 0 and don't advance input pointer. + +And then, in `consume_count' in `cplus-dem.c': + + while (isdigit ((unsigned char)**type)) + { + count *= 10; + count += **type - '0'; + /* A sanity check. Otherwise a symbol like + `_Utf390_1__1_9223372036854775807__9223372036854775' + can cause this function to return a negative value. + In this case we just consume until the end of the string. */ + if (count > strlen (*type)) + { + *type = save; + return 0; + } + +This is why a new function, for example, needs only a log entry saying +"New Function" --- all the details should be in the source. + +Avoid the temptation to abbreviate filenames or function names, as in +this example (mostly real, but slightly exaggerated): + + * gdbarch.[ch] (gdbarch_tdep, gdbarch_bfd_arch_info, + gdbarch_byte_order, {set,}gdbarch_long_bit, + {set,}gdbarch_long_long_bit, {set,}gdbarch_ptr_bit): Corresponding + functions. + +This makes it difficult for others to search the ChangeLog for changes +to the file or function they are interested in. For example, if you +searched for `set_gdbarch_long_bit', you would not find the above +entry, because the writer used CSH-style globbing to abbreviate the +list of functions. If you gave up, and made a second pass looking for +gdbarch.c, you wouldn't find that either. Consider your poor readers, +and write out the names. + + +ChangeLogs and the CVS log +-------------------------- + +CVS maintains its own logs, which you can access using the `cvs log' +command. This duplicates the information present in the ChangeLog, +but binds each entry to a specific revision, which can be helpful at +times. + +However, the CVS log is no substitute for the ChangeLog files. + +* CVS provides no easy way to see the changes made to a set of files + in chronological order. They're sorted first by filename, not by date. + +* Unless you put full ChangeLog paragraphs in your CVS log entries, it's + difficult to pull together changes that cross several files. + +* CVS doesn't segregate log entries for branches from those for the + trunk in any useful way. + +In some circumstances, though, the CVS log is more useful than the +ChangeLog, so we maintain both. When you commit a change, you should +provide appropriate text in both the ChangeLog and the CVS log. + +It is not necessary to provide CVS log entries for ChangeLog changes, +since it would simply duplicate the contents of the file itself. + + +Writing ChangeLog entries for merges +------------------------------------ + +Revision management software like CVS can introduce some confusion +when writing ChangeLog entries. For example, one might write a change +on a branch, and then merge it into the trunk months later. In that +case, what position and date should the developer use for the +ChangeLog entry --- that of the original change, or the date of the +merge? + +The principles described at the top need to hold for both the original +change and the merged change. That is: + +* On the branch (or trunk) where the change is first committed, the + ChangeLog entry should be written as normal, inserted at the top of + the ChangeLog and reflecting the date the change was committed to + the branch (or trunk). + +* When the change is then merged (to the trunk, or to another branch), + the ChangeLog entry should have the following form: + + 1999-03-26 Jim Blandy + + Merged change from foobar_20010401_branch: + + 1999-03-16 Keith Seitz + [...] + + In this case, "Jim Blandy" is doing the merge on March 26; "Keith + Seitz" is the original author of the change, who committed it to + `foobar_20010401_branch' on March 16. + + As shown here, the entry for the merge should be like any other + change --- inserted at the top of the ChangeLog, and stamped with + the date the merge was committed. It should indicate the origin of + the change, and provide the full text of the original entry, + indented to avoid being confused with a true log entry. Remember + that people looking for the merge will search for the original + changelog text, so it's important to preserve it unchanged. + + For the merge entry, we use the merge date, and not the original + date, because this is when the change appears on the trunk or branch + this ChangeLog documents. Its impact on these sources is + independent of when or where it originated. + +This approach preserves the structure of the ChangeLog (entries appear +in order, and dates reflect when they appeared), but also provides +full information about changes' origins. + diff --git a/doc/user/cvs-crossover-guide.html b/doc/user/cvs-crossover-guide.html new file mode 100644 index 000000000000..d038a3999efa --- /dev/null +++ b/doc/user/cvs-crossover-guide.html @@ -0,0 +1,906 @@ + + + + + +CVS to SVN Crossover Guide + + + + + +

CVS to SVN Crossover Guide

+ + +
+

Purpose

+ +

This document provides an alternate method of learning Subversion. + Many users dislike learning new technology via a theoretical "top + down" approach, as provided by the Subversion Book. Instead, + this document presents Subversion from the "bottom up": it shows a + CVS command or task, and then shows the equivalent task in + Subversion (along with relevant book links.) It's essentially a + re-indexing of topics covered by the book, keyed on CVS tasks.

+ +
+ + +
+

Table of Contents

+ +

Setup

+ + +

Basic Work Cycle

+ + +

Examining history

+
    +
  • Seeing history of an item
  • +
  • Comparing two versions of an item
  • +
+ +

Branching/Tagging/Merging

+
    +
  • Creating a branch
  • +
  • Moving a working copy to a branch
  • +
  • Finding the beginning of a branch
  • +
  • Porting a single change
  • +
  • Merging a whole branch
  • +
  • Reverting a committed change
  • +
  • Resurrecting deleted items
  • +
  • Creating a tag
  • +
  • Tweaking a tag
  • +
  • Seeing all tags
  • +
  • Comparing two tags
  • +
  • Seeing logs between two tags
  • +
+ +

Other tasks

+
    +
  • Using modules
  • +
  • Line endings and keywords
  • +
+ +
+ + +
+

Repository creation

+ +

Create a new repository for holding versioned data.

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
$ cvs -d /usr/local/repos init
+ +
Explanation:
+
Creates a new directory repos ready to hold RCS + files and config scripts.
+
+
+
+
Commands:
+
$ svnadmin create /usr/local/repos
+ +
Explanation:
+
Creates a new directory repos containing BerkeleyDB + files and config scripts.
+
+
+ +
+
Book References:
+
Repository Creation and Configuration
+
+ +
+ + +
+

Importing data

+ +

Populate a new repository with initial data. Assuming that you + have a tree of code in the local directory myproj/, and + you want to move this tree into the repository.

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
$ cd myproj
+
$ cvs -d /usr/local/repos import myproj/ none start
+ +
Explanation:
+ +
This copies the contents of the current working directory to + a new directory (myproj) in the CVS repository. The + CVS repository now contains a directory /myproj/ at the + top level.
+ +
+
+
+
Commands:
+
$ svn mkdir file:///usr/local/repos/tags
+
$ svn mkdir file:///usr/local/repos/branches
+
$ svn import myproj/ file:///usr/local/repos/trunk
+ +
Explanation:
+ +
Though not strictly required, we deliberately create + /tags and /branches top-level directories in + the repository, to hold tags and branches later on. Then we + import the contents of the local myproj/ directory into + a newly created /trunk directory in the + repository.
+
+
+ +
+
Book References:
+
Choosing a repository layout
+
svn import
+
+
+ + +
+

Installing a server

+ +

Make the repository available to clients via a network.

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
(too complex to demonstrate here)
+ +
Explanation:
+
Export the repository via the cvs pserver program. + It can be launched by either inetd or a + client's ssh remote request.
+ +
+
+
+
Commands:
+
(too complex to demonstrate here)
+ +
Explanation:
+
Export the repository with the Apache 2.0.x server, + or via the svnserve program. The latter can run as a + standalone daemon, can be launched by inetd, or + invoked by a client's ssh remote request.
+ +
+
+ +
+
Book References:
+
Server configuration
+
+ +
+ + +
+

Authenticating to a server

+ +

Have a network client prove its identity to a version + control server.

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
$ cvs -d :pserver:user@host:/repos command
+ +
Explanation:
+ +
When contacting a repository, the client pre-emptively + "pushes" its authentication credentials at the server.
+ +
+
+
+
Commands:
+
$ svn command URL
+
Password for 'user':  XXXXXXX
+ +
Explanation:
+ +
The client's authentication credentials are "pulled" from + the user interactively, and only when the server deems that a + challenge needs to be made. (And contrary to popular belief, + the --username and --password options are + merely values to be used if the server issues a + challenge; they do not "push" the credentials at the + server.)
+ +
+
+ +
+
Book References:
+
Network Model
+
+ +
+ + +
+

Browsing a repository

+ +

Browse the repository as a filesystem, perusing file + contents and history as well (older versions of files or + trees.)

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
(not possible with commandline client)
+ +
Explanation:
+ +
Not possible with commandline client. A third-party web + server tool such as ViewCVS must be used.
+ +
+
+
+
Commands:
+
$ svn list URL [-r rev] [-v]
+
$ svn cat URL [-r rev]
+ +
Explanation:
+ +
The svn list and svn cat commands allow + interactive browsing of a repository (and all previous states of + a repository) from the commandline. (The --verbose [-v] + switch displays full listing information.) If Apache is being + used as a Subversion server process (i.e. clients access via + http://), then the latest version of the + repository can be directly browsed by entering URL into + any web browser. Additionally, a third-party web server tool + (such as ViewCVS) can be used with Subversion.
+ +
+
+ +
+
Book References:
+
svn list
+
+ +
+ + +
+

Checking out a working copy

+ +

Create a workspace on local disk which mirrors a directory + in the repository.

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
$ cvs -d /usr/local/repos checkout myproj
+
U myproj/foo.c
+
U myproj/bar.c
+
+ +
Explanation:
+ +
Creates a local directory myproj which is a mirror + of the repository directory /myproj.
+ +
+
+
+
Commands:
+
$ svn checkout file:///usr/local/repos/trunk myproj
+
A  myproj/foo.c
+
A  myproj/bar.c
+
+ +
Explanation:
+ +
Assuming that the original project data was imported into + the repository /trunk directory, this creates a local + directory myproj which is a mirror of the repository + directory /trunk. Standard Subversion convention is to + do "mainline" development in /trunk. See branching and + tagging sections for more details.
+ +
+
+ +
+
Book References:
+
Initial Checkout
+
svn checkout
+
+ +
+ + +
+

Seeing locally changed items

+ +

Discover which items in the working copy have local + modifications or are scheduled for addition/deletion.

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
$ cvs status
+
+
File: baz.c   Status: Up-to-date
+
+
$ cvs update
+
M foo.c
+
U bar.c
+
+ +
Explanation:
+ +
The cvs status command shows whether a file is + locally modified or out of date, including information about + working revision and branch info. Unfortunately, because the + output is so verbose and hard to read, many users run cvs + update instead, which shows a more compact listing of + modified files (and of course, it also causes the server to + merge changes into your working copy.)
+ +
+
+
+
Commands:
+
$ svn status
+
M     foo.c
+
+ +
Explanation:
+ +
Shows modified files only. Very fast, as it does not use + the network. Does not update your working copy, yet still shows + a single-line display, much like svn update. To see + working revision and branch information, run svn info.
+ +
+
+ +
+
Book References:
+
Examine Your Changes
+
svn status
+
+ +
+ + +
+

Seeing out-of-date items

+ +

Discover which items in the working copy are out-of-date + (i.e. newer versions exist in the repository.)

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
$ cvs status
+
+
File: baz.c   Status: Needs Patch
+
+
$ cvs -n update
+
M foo.c
+
U bar.c
+
+ +
Explanation:
+ +
The cvs status command shows whether a file is + locally modified or out of date, including information about + working revision and branch info. A less verbose option is to + run cvs -n update instead, which shows a compact + listing of both out-of-date and locally modified files, without + actually updating the working copy.
+ +
+
+
+
Commands:
+
$ svn status -u
+
M     46     foo.c
+
M  *  46     bar.c
+
   *  46     baz.c
+
+ +
Explanation:
+ +
Shows modified files (M) as well as out-of-date + files (*). Contacts repository, but doesn't modify the + working copy. To see working revision and branch information, + run svn info.
+ +
+
+ +
+
Book References:
+
Examine Your Changes
+
svn status
+
+ +
+ + +
+

Scheduling additions or deletions

+ +

Schedule a working-copy file or directory to be added or + removed from the repository.

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
$ touch foo.c
+
$ cvs add foo.c
+
cvs server: scheduling file `blah' for addition
+
cvs server: use 'cvs commit' to add this file permanently
+
 
+
$ mkdir new-dir
+
$ cvs add new-dir
+
Directory new-dir added to the repository
+
 
+
$ rm bar.c
+
$ cvs rm bar.c
+
cvs remove: scheduling `bar.c' for removal
+
cvs remove: use 'cvs commit' to remove this file permanently
+
 
+
$ rm -rf old-dir/*
+
$ cvs rm old-dir
+
cvs remove: Removing 3bits
+
+ + +
Explanation:
+ +
Schedules a file or directory for addition or removal + to/from the repository. The repository will not be changed + until the user runs cvs commit, except for the case of + adding a directory, which immediately changes the repository. + Also, directories cannot be truly removed from the repository, + just emptied out. (cvs update -P will prune empty + directories from your working copy.)
+ +
+
+
+
Commands:
+
$ touch foo.c
+
$ svn add foo.c
+
A     foo.c
+
 
+
$ mkdir new-dir
+
$ svn add new-dir
+
A     new-dir
+
 
+
$ svn rm bar.c
+
D     bar.c
+
 
+
$ svn rm old-dir
+
D     old-dir/file1
+
D     old-dir/file2
+
+ +
Explanation:
+ +
Schedules a file or directory for addition or removal + to/from the repository. The repository will not be changed + until the user runs svn commit. The scheduled + operations are shown as A or D by svn + status, and svn revert can un-do the scheduling. + Directories really can be deleted (though as with all deleted + items, continues to exist in history.)
+ +
+
+ +
+
Book References:
+
Make Changes to Your Working Copy
+
svn add
+
svn delete
+
+ +
+ + +
+

Copying and moving

+ +

Copy or move/rename a file or directory.

+ + + + + + + + + + +
CVSSubversion
+
+
Commands:
+
(not possible.)
+ + +
Explanation:
+ +
Not possible, unless an administrator directly mucks with + RCS files in the repository. (And in that case, no history + records the act of copying or renaming.)
+ +
+
+
+
Commands:
+
$ svn copy foo.c foo2.c
+
A     foo2.c
+
 
+
$ svn copy dir dir2
+
A     dir2
+
 
+
$ svn move bar.c baz.c
+
A     baz.c
+
D     bar.c
+
 
+
$ svn move dirA dirB
+
A     dirB
+
D     dirA/file1
+
D     dirA/file2
+
+ +
Explanation:
+ +
The svn copy command schedules a file or directory + for addition to the repository, recording the "source" of the + copy. After committing, svn log on the copied item + will trace history back through the original copy-source. The + svn move command is exactly equivalent to running + svn copy, followed by an svn delete on the + copy-source: the result is a new item scheduled for addition + (with copy-history attached) and the original item scheduled for + deletion.
+ +
+
+ +
+
Book References:
+
Make Changes to Your Working Copy
+
svn copy
+
svn move
+
+ + +
+ + +
+

Finding the beginning of a branch

+ +

If you're attempting to merge an entire branch into another, you +need to compare the "root" and "tip" of the source branch, and then +merge those differences into a working copy of the target branch. +Obviously the "tip" of the branch can be represented by using the +HEAD keyword. But how do you find the "birth" revision of +the source branch?

+ +

The easiest solution is to run

+ +
+   $ svn log -v --stop-on-copy source-branch-URL
+   …
+
+ +

This command will display every change ever made to the branch, but +--stop-on-copy option will cause the output to stop as soon +as detects a copy operation in the branch's history. By definition, +then, the very last log entry printed will show the copy being made. +It will look something like:

+ +
+r9189 | joe | 2004-03-22 10:10:47 -0600 (Mon, 22 Mar 2004) | 1 line
+Changed paths:
+   A /branches/mybranch (from /trunk:9188)
+
+ +

In this case, you would then know to compare revisions 9189 and +HEAD of the branch in order to perform the merge:

+ +
+   $ svn merge -r9189:HEAD source-branch-URL target-branch-WC
+   …
+
+ +
+ + +
+

Seeing all of a project's tags

+ +

Assuming you've been following a consistent policy for creating +tag-copies, then this is just a matter of running svn ls on a +directory containing your tags. Typically you would run it on the +/tags directory in your repository, although you're certainly +free to organize this directory in a more complex way, or invent a +different convention altogether.

+ +

As an example, you can see all of Subversion's tags by running:

+ +
+   $ svn ls --verbose http://svn.apache.org/repos/asf/subversion/tags
+     …
+       7739 kfogel              Nov 13 22:05 0.33.0/
+       7796 josander            Nov 18 12:15 0.33.1/
+       7932 josander            Dec 03 17:54 0.34.0/
+       8045 josander            Dec 19 15:13 0.35.0/
+       8063 josander            Dec 20 11:20 0.35.1/
+       8282 josander            Jan 13 14:15 0.36.0/
+       8512 josander            Jan 24 17:31 0.37.0/
+       8810 kfogel              Feb 23 03:44 1.0.0/
+     …
+
+ +
+ + +
+

Seeing the differences between two tags

+ +

Just use svn diff in its fully expanded form, which +compares any two URLs:

+ +
+   $ svn diff tagURL1 tagURL2
+   …
+
+ +
+ + +
+

Seeing logs between two tags

+ +

This is a somewhat common practice in CVS, and is doable in Subversion, +but requires a little bit more work. Assuming that you've made two +tags of /trunk at different points in time, the ultimate goal +here is to run

+ +
+   $ svn log -rX:Y trunkURL
+
+ +

…where X and Y are the revisions from which the two tags were +copied. To discover X and Y, you can use the same technique +described in the previous section ("finding the beginning of a +branch".) Just use the --stop-on-copy option when logging the +history of each tag. No commits happen on tag directories, so the +following commands should each produce exactly one log +entry:

+ +
+   $ svn log -v --stop-on-copy tag1-URL
+
+   r3520 | joe | 2004-03-12 15:28:43 -0600 (Fri, 12 Mar 2004) | 1 line
+   …
+
+   $ svn log -v --stop-on-copy tag2-URL
+   a
+   r4177 | joe | 2004-03-12 15:28:43 -0600 (Fri, 12 Mar 2004) | 1 line
+   …
+
+ +

So in this example, the values of X and Y are 3520 and 4177. Now +you can view all /trunk changes between those two points in time:

+ +
+   $ svn log -r3520:4177 trunkURL
+   …
+
+ +
+ + +
+

Fixing an incorrect tag

+ +

If your tag is a bit off, you can "adjust" it just as people often +do in CVS. Simply check out a working copy of the tag directory, make +any changes you wish, and commit.

+ +

Remember, because branches and tags are directories, they can also +be deleted when they're no longer of any use to your project. They'll +continue to exist in the repository's history.

+ + +
+ + +
+

Creating/using "modules"

+ +

Compare CVS Modules vs. svn:externals.

+ +
+ + + + diff --git a/doc/user/lj_article.txt b/doc/user/lj_article.txt new file mode 100644 index 000000000000..dca776d8947a --- /dev/null +++ b/doc/user/lj_article.txt @@ -0,0 +1,323 @@ + + The Subversion Project: Building a Better CVS + ============================================== + + Ben Collins-Sussman + + Written in August 2001 + Published in Linux Journal, January 2002 + +Abstract +-------- + +This article discusses the history, goals, features and design of +Subversion (http://subversion.tigris.org), an open-source project that +aims to produce a compelling replacement for CVS. + + +Introduction +------------ + +If you work on any kind of open-source project, you've probably worked +with CVS. You probably remember the first time you learned to do an +anonymous checkout of a source tree over the net -- or your first +commit, or learning how to look at CVS diffs. And then the fateful +day came: you asked your friend how to rename a file. + +"You can't", was the reply. + +What? What do you mean? + +"Well, you can delete the file from the repository and then re-add it +under a new name." + +Yes, but then nobody would know it had been renamed... + +"Let's call the CVS administrator. She can hand-edit the repository's +RCS files for us and possibly make things work." + +What? + +"And by the way, don't try to delete a directory either." + +You rolled your eyes and groaned. How could such simple tasks be +difficult? + + +The Legacy of CVS +----------------- + +No doubt about it, CVS has evolved into the standard Software +Configuration Management (SCM) system of the open source community. +And rightly so! CVS itself is Free software, and its wonderful "non +locking" development model -- whereby dozens of far-flung programmers +collaborate -- fits the open-source world very well. In fact, one +might argue that without CVS, it's doubtful whether sites like +Freshmeat or Sourceforge would ever have flourished as they do now. +CVS and its semi-chaotic development model have become an essential +part of open source culture. + +So what's wrong with CVS? + +Because it uses the RCS storage-system under the hood, CVS can only +track file contents, not tree structures. As a result, the user has +no way to copy, move, or rename items without losing history. Tree +rearrangements are always ugly server-side tweaks. + +The RCS back-end cannot store binary files efficiently, and branching +and tagging operations can grow to be very slow. CVS also uses the +network inefficiently; many users are annoyed by long waits, because +file differeces are sent in only one direction (from server to client, +but not from client to server), and binary files are always +transmitted in their entirety. + +From a developer's standpoint, the CVS codebase is the result of +layers upon layers of historical "hacks". (Remember that CVS began +life as a collection of shell-scripts to drive RCS.) This makes the +code difficult to understand, maintain, or extend. For example: CVS's +networking ability was essentially "stapled on". It was never +designed to be a native client-server system. + +Rectifying CVS's problems is a huge task -- and we've only listed just +a few of the many common complaints here. + + +Enter Subversion +---------------- + +In 1995, Karl Fogel and Jim Blandy founded Cyclic Software, a company +for commercially supporting and improving CVS. Cyclic made the first +public release of a network-enabled CVS (contributed by Cygnus +software.) In 1999, Karl Fogel published a book about CVS and the +open-source development model it enables (cvsbook.red-bean.com). Karl +and Jim had long talked about writing a replacement for CVS; Jim had +even drafted a new, theoretical repository design. Finally, in +February of 2000, Brian Behlendorf of CollabNet (www.collab.net) +offered Karl a full-time job to write a CVS replacement. Karl +gathered a team together and work began in May. + +The team settled on a few simple goals: it was decided that Subversion +would be designed as a functional replacement for CVS. It would do +everything that CVS does -- preserving the same development model +while fixing the flaws in CVS's (lack-of) design. Existing CVS users +would be the target audience: any CVS user should be able to start +using Subversion with little effort. Any other SCM "bonus features" +were decided to be of secondary importance (at least before a 1.0 +release.) + +At the time of writing, the original team has been coding for a little +over a year, and we have a number of excellent volunteer contributors. +(Subversion, like CVS, is a open-source project!) + + +Subversion's Features +---------------------- + +Here's a quick run-down of some of the reasons you should be excited +about Subversion: + + * Real copies and renames. The Subversion repository doesn't use + RCS files at all; instead, it implements a 'virtual' versioned + filesystem that tracks tree-structures over time (described + below). Files *and* directories are versioned. At last, there + are real client-side `mv' and `cp' commands that behave just as + you think. + + * Atomic commits. A commit either goes into the repository + completely, or not all. + + * Advanced network layer. The Subversion network server is Apache, + and client and server speak WebDAV(2) to one another. (See the + 'design' section below.) + + * Faster network access. A binary diffing algorithm is used to + store and transmit deltas in *both* directions, regardless of + whether a file is of text or binary type. + + * Filesystem "properties". Each file or directory has an invisible + hashtable attached. You can invent and store any arbitrary + key/value pairs you wish: owner, perms, icons, app-creator, + mime-type, personal notes, etc. This is a general-purpose feature + for users. Properties are versioned, just like file contents. + And some properties are auto-detected, like the mime-type of a + file (no more remembering to use the '-kb' switch!) + + * Extensible and hackable. Subversion has no historical baggage; it + was designed and then implemented as a collection of shared C + libraries with well-defined APIs. This makes Subversion extremely + maintainable and usable by other applications and languages. + + * Easy migration. The Subversion command-line client is very + similar to CVS; the development model is the same, so CVS users + should have little trouble making the switch. Development of a + 'cvs2svn' repository converter is in progress. + + * It's Free. Subversion is released under a Apache/BSD-style + open-source license. + + +Subversion's Design +------------------- + +Subversion has a modular design; it's implemented as a collection of C +libraries. Each layer has a well-defined purpose and interface. In +general, code flow begins at the top of the diagram and flows +"downward" -- each layer provides an interface to the layer above it. + + <> + + +Let's take a short tour of these layers, starting at the bottom. + + +--> The Subversion filesystem. + +The Subversion Filesystem is *not* a kernel-level filesystem that one +would install in an operating system (like the Linux ext2 fs.) +Instead, it refers to the design of Subversion's repository. The +repository is built on top of a database -- currently Berkeley DB -- +and thus is a collection of .db files. However, a library accesses +these files and exports a C API that simulates a filesystem -- +specifically, a "versioned" filesystem. + +This means that writing a program to access the repository is like +writing against other filesystem APIs: you can open files and +directories for reading and writing as usual. The main difference is +that this particular filesystem never loses data when written to; old +versions of files and directories are always saved as historical +artifacts. + +Whereas CVS's backend (RCS) stores revision numbers on a per-file +basis, Subversion numbers entire trees. Each atomic 'commit' to the +repository creates a completely new filesystem tree, and is +individually labeled with a single, global revision number. Files and +directories which have changed are rewritten (and older versions are +backed up and stored as differences against the latest version), while +unchanged entries are pointed to via a shared-storage mechanism. This +is how the repository is able to version tree structures, not just +file contents. + +Finally, it should be mentioned that using a database like Berkeley DB +immediately provides other nice features that Subversion needs: data +integrity, atomic writes, recoverability, and hot backups. (See +www.sleepycat.com for more information.) + + +--> The network layer. + +Subversion has the mark of Apache all over it. At its very core, the +client uses the Apache Portable Runtime (APR) library. (In fact, this +means that Subversion client should compile and run anywhere Apache +httpd does -- right now, this list includes all flavors of Unix, +Win32, BeOS, OS/2, Mac OS X, and possibly Netware.) + +However, Subversion depends on more than just APR -- the Subversion +"server" is Apache httpd itself. + +Why was Apache chosen? Ultimately, the decision was about not +reinventing the wheel. Apache is a time-tested, open-source server +process that ready for serious use, yet is still extensible. It can +sustain a high network load. It runs on many platforms and can +operate through firewalls. It's able to use a number of different +authentication protocols. It can do network pipelining and caching. +By using Apache as a server, Subversion gets all these features for +free. Why start from scratch? + +Subversion uses WebDAV as its network protocol. DAV (Distributed +Authoring and Versioning) is a whole discussion in itself (see +www.webdav.org) -- but in short, it's an extension to HTTP that allows +reads/writes and "versioning" of files over the web. The Subversion +project is hoping to ride a slowly rising tide of support for this +protocol: all of the latest file-browsers for Win32, MacOS, and GNOME +speak this protocol already. Interoperability will (hopefully) become +more and more of a bonus over time. + +For users who simply wish to access Subversion repositories on local +disk, the client can do this too; no network is required. The +"Repository Access" layer (RA) is an abstract API implemented by both +the DAV and local-access RA libraries. This is a specific benefit of +writing a "librarized" version control system; it's a big win over +CVS, which has two very different, difficult-to-maintain codepaths for +local vs. network repository-access. Feel like writing a new network +protocol for Subversion? Just write a new library that implements the +RA API! + + +--> The client libraries. + +On the client side, the Subversion "working copy" library maintains +administrative information within special SVN/ subdirectories, similar +in purpose to the CVS/ administrative directories found in CVS working +copies. + +A glance inside the typical SVN/ directory turns up a bit more than +usual, however. The `entries' file contains XML which describes the +current state of the working copy directory (and which basically +serves the purposes of CVS's Entries, Root, and Repository files +combined). But other items present (and not found in CVS/) include +storage locations for the versioned "properties" (the metadata +mentioned in 'Subversion Features' above) and private caches of +pristine versions of each file. This latter feature provides the +ability to report local modifications -- and do reversions -- +*without* network access. Authentication data is also stored within +SVN/, rather than in a single .cvspass-like file. + +The Subversion "client" library has the broadest responsibility; its +job is to mingle the functionality of the working-copy library with +that of the repository-access library, and then to provide a +highest-level API to any application that wishes to perform general +version control actions. + +For example: the C routine `svn_client_checkout()' takes a URL as an +argument. It passes this URL to the repository-access library and +opens an authenticated session with a particular repository. It then +asks the repository for a certain tree, and sends this tree into the +working-copy library, which then writes a full working copy to disk +(SVN/ directories and all.) + +The client library is designed to be used by any application. While +the Subversion source code includes a standard command-line client, it +should be very easy to write any number of GUI clients on top of the +client library. Hopefully, these GUIs should someday prove to be much +better than the current crop of CVS GUI applications (the majority of +which are no more than fragile "wrappers" around the CVS command-line +client.) + +In addition, proper SWIG bindings (www.swig.org) should make +the Subversion API available to any number of languages: java, perl, +python, guile, and so on. In order to Subvert CVS, it helps to be +ubiquitous! + + +Subversion's Future +------------------- + +The release of Subversion 1.0 is currently planned for early 2002. +After the release of 1.0, Subversion is slated for additions such as +i18n support, "intelligent" merging, better "changeset" manipulation, +client-side plugins, and improved features for server administration. +(Also on the wishlist is an eclectic collection of ideas, such as +distributed, replicating repositories.) + +A final thought from Subversion's FAQ: + + "We aren't (yet) attempting to break new ground in SCM systems, nor + are we attempting to imitate all the best features of every SCM + system out there. We're trying to replace CVS." + +If, in three years, Subversion is widely presumed to be the "standard" +SCM system in the open-source community, then the project will have +succeeded. But the future is still hazy: ultimately, Subversion +will have to win this position on its own technical merits. + +Patches are welcome. + + +For More Information +-------------------- + +Please visit the Subversion project website at +http://subversion.tigris.org. There are discussion lists to join, and +the source code is available via anonymous CVS -- and soon through +Subversion itself. + diff --git a/doc/user/svn-best-practices.html b/doc/user/svn-best-practices.html new file mode 100644 index 000000000000..61ba1c6e53a5 --- /dev/null +++ b/doc/user/svn-best-practices.html @@ -0,0 +1,350 @@ + + + + + + +Subversion Best Practices + + + + + +

Subversion Best Practices

+ +

This is a quick set of guidelines for making the best use of +Subversion in your day-to-day software development work.

+ + +

Use a sane repository layout

+ +

There are many ways to lay out your repository. Because branches +and tags are ordinary directories, you'll need to account for them in +your repository structure.

+ +

The Subversion project officially recommends the idea of a "project +root", which represents an anchoring point for a project. A "project +root" contains exactly three subdirectories: /trunk, +/branches, and /tags. A repository may contain +only one project root, or it may contain a number of them.

+ +

Book reference: Choosing + a Repository Layout.

+ + + + + +

Commit logical changesets

+ +

When you commit a change to the repository, make sure your change +reflects a single purpose: the fixing of a specific bug, the addition +of a new feature, or some particular task. Your commit will create a +new revision number which can forever be used as a "name" for the +change. You can mention this revision number in bug databases, or use +it as an argument to svn merge should you want to undo the +change or port it to another branch.

+ +

Book reference: "Subversion and Changesets" sidebar, + within chapter + 4.

+ + + +

Use the issue-tracker wisely

+ +

Try to create as many two-way links between Subversion changesets +and your issue-tracking database as possible:

+ +
    +
  • If possible, refer to a specific issue ID in every commit log message.
  • +
  • When appending information to an issue (to describe progress, or + to close the issue) name the revision number(s) responsible + for the change.
  • +
+ + + +

Track merges manually

+ +

When committing the result of a merge, be sure to write a +descriptive log message that explains what was merged, something +like:

+ +
Merged revisions 3490:4120 of /branches/foobranch to /trunk.
+ +

Book reference: Tracking + merges manually, and Merging a whole branch to another.

+ + + +

Understand mixed-revision working copies

+ +

Your working copy's directories and files can be at different +"working" revisions: this is a deliberate feature which allows you to +mix and match older versions of things with newer ones. But there are +few facts you must be aware of:

+ +
    + +
  1. After every svn commit, your working copy has mixed +revisions. The things you just committed are now at the HEAD +revision, and everything else is at an older revision.
  2. + +
  3. Certain commits are disallowed: +
      +
    • You cannot commit the deletion of a file or directory which + doesn't have a working revision of HEAD.
    • +
    • You cannot commit a property change to a directory which + doesn't have a working revision of HEAD.
    • +
    +
  4. + +
  5. svn update will bring your entire working copy to one + working revision, and is the typical solution to the + problems mentioned in point #2.
  6. +
+ +

Book reference: The + limitation of mixed revisions.

+ + + + +

Be patient with large files

+ +

A nice feature of Subversion is that by design, there is no limit +to the size of files it can handle. Files are sent "streamily" in +both directions between Subversion client and server, using a small, +constant amount of memory on each side of the network.

+ +

Of course, there are a number of practical issues to consider. +While there's no need to worry about files in the kilobyte-sized range +(e.g. typical source-code files), committing larger files can take a +tremendous amount of both time and space (e.g. files that are dozens +or hundreds of megabytes large.)

+ +

To begin with, remember that your Subversion working copy stores +pristine copies of all version-controlled files in the +.svn/text-base/ area. This means that your working copy +takes up at least twice as much disk space as the original dataset. +Beyond that, the Subversion client follows a (currently unadjustable) +algorithm for committing files:

+ +
    +
  • Copies the file to .svn/tmp/ (can take a while, + and temporarily uses extra disk space))
  • + +
  • Performs a binary diff between the tmpfile and the pristine + copy, or between the tmpfile and an empty-file if newly + added. (can take a very long time to compute, even + though only a small amount of data might ultimately be sent + over the network)
  • + +
  • Sends the diff to the server, then moves the tmpfile into + .svn/text-base/
  • +
+ +

So while there's no theoretical limit to the size of your files, +you'll need to be aware that very large files may require quite a bit +of patient waiting while your client chugs away. You can rest +assured, however, that unlike CVS, your large files won't incapacitate +the server or affect other users.

+ + + +

Work around commands that don't understand copies/renames

+ +

When a file or directory is copied or renamed, the Subversion repository +tracks that history. Unfortunately in Subversion 1.0, the only client +subcommand which actually takes advantage of this feature is svn +log. A number of other commands (such as svn diff and +svn cat) ought to be automatically following rename-history, +but aren't doing so yet.

+ +

In all of these cases, a basic workaround is to use 'svn log +-v' to discover the proper path within the older revision.

+ +

For example, suppose you copied /trunk to +/branches/mybranch in revision 200, and then committed some +changes to /branches/mybranch/foo.c in subsequent revisions. +Now you'd like to compare revisions 80 and 250 of the file.

+ +

If you have a working copy of the branch and run svn diff +-r80:250 foo.c, you'll see an error about +/branches/mybranch/foo.c not existing in revision 80. To +remedy, you would run svn log -v on your branch or file to +discover that it was named /trunk/foo.c prior to revision 200, +and then compare the two URLs directly:

+ +
+   $ svn diff http://.../trunk/foo.c@80 \
+              http://.../branches/mybranch/foo.c@200
+
+ + + + + +

Know when to create branches

+ +

This is a hotly debated question, and it really depends on the +culture of your software project. Rather than prescribe a universal +policy, we'll describe three common ones here.

+ +

The Never-Branch system

+ +

(Often used by nascent projects that don't yet have runnable code.)

+ +
    +
  • Users commit their day-to-day work on /trunk.
  • +
  • Occasionally /trunk "breaks" (doesn't compile, or fails +functional tests) when a user begins to commit a series of complicated +changes.
  • +
+ +

Pros: Very easy policy to follow. New developers have low + barrier to entry. Nobody needs to learn how to branch or merge.

+ +

Cons: Chaotic development, code could be unstable at any + time.

+ +

A side note: this sort of development is a bit less risky in +Subversion than in CVS. Because Subversion commits are atomic, it's +not possible for a checkout or update to receive a "partial" commit +while somebody else is in the process of committing.

+ + +

The Always-Branch system

+ +

(Often used by projects that favor heavy management and supervision.)

+ +
    +
  • Each user creates/works on a private branch for every coding task. +
  • +
  • When coding is complete, someone (original coder, peer, or + manager) reviews all private branch changes and merges them to + /trunk.
  • +
+ +

Pros: /trunk is guaranteed to be + extremely stable at all times.

+ +

Cons: Coders are artificially isolated from each other, + possibly creating more merge conflicts than necessary. + Requires users to do lots of extra merging.

+ + +

The Branch-When-Needed system

+ +

(This is the system used by the Subversion project.) + +

    +
  • Users commit their day-to-day work on /trunk.
  • + +
  • Rule #1: /trunk must compile and pass regression tests at +all times. Committers who violate this rule are publically +humiliated.
  • + +
  • Rule #2: a single commit (changeset) must not be so large +so as to discourage peer-review.
  • + +
  • Rule #3: if rules #1 and #2 come into conflict (i.e. it's +impossible to make a series of small commits without disrupting the +trunk), then the user should create a branch and commit a series of +smaller changesets there. This allows peer-review without disrupting +the stability of /trunk.
  • + +
+ +

Pros: /trunk is guaranteed to be stable at all + times. The hassle of branching/merging is somewhat rare.

+ +

Cons: Adds a bit of burden to users' daily work: + they must compile and test before every commit.

+ + + + + + + diff --git a/gen-make.opts b/gen-make.opts new file mode 100644 index 000000000000..96d052ece8de --- /dev/null +++ b/gen-make.opts @@ -0,0 +1,2 @@ +[options] +--release = diff --git a/gen-make.py b/gen-make.py new file mode 100755 index 000000000000..2b49b5efb0da --- /dev/null +++ b/gen-make.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# +# gen-make.py -- generate makefiles for building Subversion +# + + +import os +import sys +import getopt +try: + my_getopt = getopt.gnu_getopt +except AttributeError: + my_getopt = getopt.getopt +try: + # Python >=3.0 + import configparser +except ImportError: + # Python <3.0 + import ConfigParser as configparser + +# for the generator modules +sys.path.insert(0, os.path.join('build', 'generator')) + +# for getversion +sys.path.insert(1, 'build') + +gen_modules = { + 'make' : ('gen_make', 'Makefiles for POSIX systems'), + 'dsp' : ('gen_msvc_dsp', 'MSVC 6.x project files'), + 'vcproj' : ('gen_vcnet_vcproj', 'VC.Net project files'), + } + +def main(fname, gentype, verfname=None, + skip_depends=0, other_options=None): + if verfname is None: + verfname = os.path.join('subversion', 'include', 'svn_version.h') + + gen_module = __import__(gen_modules[gentype][0]) + + generator = gen_module.Generator(fname, verfname, other_options) + + if not skip_depends: + generator.compute_hdr_deps() + + generator.write() + generator.write_sqlite_headers() + + if ('--debug', '') in other_options: + for dep_type, target_dict in generator.graph.deps.items(): + sorted_targets = list(target_dict.keys()); sorted_targets.sort() + for target in sorted_targets: + print(dep_type + ": " + _objinfo(target)) + for source in target_dict[target]: + print(" " + _objinfo(source)) + print("=" * 72) + gen_keys = sorted(generator.__dict__.keys()) + for name in gen_keys: + value = generator.__dict__[name] + if isinstance(value, list): + print(name + ": ") + for i in value: + print(" " + _objinfo(i)) + print("=" * 72) + + +def _objinfo(o): + if isinstance(o, str): + return repr(o) + else: + t = o.__class__.__name__ + n = getattr(o, 'name', '-') + f = getattr(o, 'filename', '-') + return "%s: %s %s" % (t,n,f) + + +def _usage_exit(err=None): + "print ERR (if any), print usage, then exit the script" + if err: + print("ERROR: %s\n" % (err)) + print("USAGE: gen-make.py [options...] [conf-file]") + print(" -s skip dependency generation") + print(" --debug print lots of stuff only developers care about") + print(" --release release mode") + print(" --reload reuse all options from the previous invocation") + print(" of the script, except -s, -t, --debug and --reload") + print(" -t TYPE use the TYPE generator; can be one of:") + items = sorted(gen_modules.items()) + for name, (module, desc) in items: + print(' %-12s %s' % (name, desc)) + print("") + print(" The default generator type is 'make'") + print("") + print(" Makefile-specific options:") + print("") + print(" --assume-shared-libs") + print(" omit dependencies on libraries, on the assumption that") + print(" shared libraries will be built, so that it is unnecessary") + print(" to relink executables when the libraries that they depend") + print(" on change. This is an option for developers who want to") + print(" increase the speed of frequent rebuilds.") + print(" *** Do not use unless you understand the consequences. ***") + print("") + print(" UNIX-specific options:") + print("") + print(" --installed-libs") + print(" Comma-separated list of Subversion libraries to find") + print(" pre-installed instead of building (probably only") + print(" useful for packagers)") + print("") + print(" Windows-specific options:") + print("") + print(" --with-apr=DIR") + print(" the APR sources are in DIR") + print("") + print(" --with-apr-util=DIR") + print(" the APR-Util sources are in DIR") + print("") + print(" --with-apr-iconv=DIR") + print(" the APR-Iconv sources are in DIR") + print("") + print(" --with-berkeley-db=DIR") + print(" look for Berkeley DB headers and libs in") + print(" DIR") + print("") + print(" --with-serf=DIR") + print(" the Serf sources are in DIR") + print("") + print(" --with-httpd=DIR") + print(" the httpd sources and binaries required") + print(" for building mod_dav_svn are in DIR;") + print(" implies --with-apr{-util, -iconv}, but") + print(" you can override them") + print("") + print(" --with-libintl=DIR") + print(" look for GNU libintl headers and libs in DIR;") + print(" implies --enable-nls") + print("") + print(" --with-openssl=DIR") + print(" tell serf to look for OpenSSL headers") + print(" and libs in DIR") + print("") + print(" --with-zlib=DIR") + print(" tell Subversion to look for ZLib headers and") + print(" libs in DIR") + print("") + print(" --with-jdk=DIR") + print(" look for the java development kit here") + print("") + print(" --with-junit=DIR") + print(" look for the junit jar here") + print(" junit is for testing the java bindings") + print("") + print(" --with-swig=DIR") + print(" look for the swig program in DIR") + print("") + print(" --with-sqlite=DIR") + print(" look for sqlite in DIR") + print("") + print(" --with-sasl=DIR") + print(" look for the sasl headers and libs in DIR") + print("") + print(" --enable-pool-debug") + print(" turn on APR pool debugging") + print("") + print(" --enable-purify") + print(" add support for Purify instrumentation;") + print(" implies --enable-pool-debug") + print("") + print(" --enable-quantify") + print(" add support for Quantify instrumentation") + print("") + print(" --enable-nls") + print(" add support for gettext localization") + print("") + print(" --enable-bdb-in-apr-util") + print(" configure APR-Util to use Berkeley DB") + print("") + print(" --enable-ml") + print(" enable use of ML assembler with zlib") + print("") + print(" --disable-shared") + print(" only build static libraries") + print("") + print(" --with-static-apr") + print(" Use static apr and apr-util") + print("") + print(" --with-static-openssl") + print(" Use static openssl") + print("") + print(" --vsnet-version=VER") + print(" generate for VS.NET version VER (2002, 2003, 2005, 2008, 2010 or 2012)") + print(" [only valid in combination with '-t vcproj']") + print("") + print(" --with-apr_memcache=DIR") + print(" the apr_memcache sources are in DIR") + sys.exit(1) + + +class Options: + def __init__(self): + self.list = [] + self.dict = {} + + def add(self, opt, val): + if opt in self.dict: + self.list[self.dict[opt]] = (opt, val) + else: + self.dict[opt] = len(self.list) + self.list.append((opt, val)) + +if __name__ == '__main__': + try: + opts, args = my_getopt(sys.argv[1:], 'st:', + ['debug', + 'release', + 'reload', + 'assume-shared-libs', + 'with-apr=', + 'with-apr-util=', + 'with-apr-iconv=', + 'with-berkeley-db=', + 'with-serf=', + 'with-httpd=', + 'with-libintl=', + 'with-openssl=', + 'with-zlib=', + 'with-jdk=', + 'with-junit=', + 'with-swig=', + 'with-sqlite=', + 'with-sasl=', + 'with-apr_memcache=', + 'with-static-apr', + 'with-static-openssl', + 'enable-pool-debug', + 'enable-purify', + 'enable-quantify', + 'enable-nls', + 'enable-bdb-in-apr-util', + 'enable-ml', + 'disable-shared', + 'installed-libs=', + 'vsnet-version=', + + # Keep distributions that help by adding a path + # working. On unix this would be filtered by + # configure, but on Windows gen-make.py is used + # directly. + 'with-neon=', + 'without-neon', + ]) + if len(args) > 1: + _usage_exit("Too many arguments") + except getopt.GetoptError, e: + _usage_exit(str(e)) + + conf = 'build.conf' + skip = 0 + gentype = 'make' + rest = Options() + + if args: + conf = args[0] + + # First merge options with previously saved to gen-make.opts if --reload + # options used + for opt, val in opts: + if opt == '--reload': + prev_conf = configparser.ConfigParser() + prev_conf.read('gen-make.opts') + for opt, val in prev_conf.items('options'): + if opt != '--debug': + rest.add(opt, val) + del prev_conf + elif opt == '--with-neon' or opt == '--without-neon': + # Provide a warning that we ignored these arguments + print("Ignoring no longer supported argument '%s'" % opt) + else: + rest.add(opt, val) + + # Parse options list + for opt, val in rest.list: + if opt == '-s': + skip = 1 + elif opt == '-t': + gentype = val + else: + if opt == '--with-httpd': + rest.add('--with-apr', os.path.join(val, 'srclib', 'apr')) + rest.add('--with-apr-util', os.path.join(val, 'srclib', 'apr-util')) + rest.add('--with-apr-iconv', os.path.join(val, 'srclib', 'apr-iconv')) + + # Remember all options so that --reload and other scripts can use them + opt_conf = open('gen-make.opts', 'w') + opt_conf.write('[options]\n') + for opt, val in rest.list: + opt_conf.write(opt + ' = ' + val + '\n') + opt_conf.close() + + if gentype not in gen_modules.keys(): + _usage_exit("Unknown module type '%s'" % (gentype)) + + main(conf, gentype, skip_depends=skip, other_options=rest.list) + + +### End of file. diff --git a/get-deps.sh b/get-deps.sh new file mode 100755 index 000000000000..5d8f49cd8798 --- /dev/null +++ b/get-deps.sh @@ -0,0 +1,173 @@ +#!/bin/sh +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# +# get-deps.sh -- download the dependencies useful for building Subversion +# + +# If changing this file please take care to try to make your changes as +# portable as possible. That means at a minimum only use POSIX supported +# features and functions. However, it may be desirable to use an even +# more narrow set of features than POSIX, e.g. Solaris /bin/sh only has +# a subset of the POSIX shell features. If in doubt, limit yourself to +# features already used in the file. Reviewing the history of changes +# may be useful as well. + +APR_VERSION=${APR_VERSION:-"1.4.6"} +APU_VERSION=${APU_VERSION:-"1.5.1"} +SERF_VERSION=${SERF_VERSION:-"1.2.1"} +ZLIB_VERSION=${ZLIB_VERSION:-"1.2.8"} +SQLITE_VERSION=${SQLITE_VERSION:-"3.7.15.1"} +GTEST_VERSION=${GTEST_VERSION:-"1.6.0"} +HTTPD_VERSION=${HTTPD_VERSION:-"2.4.3"} +APR_ICONV_VERSION=${APR_ICONV_VERSION:-"1.2.1"} + +APR=apr-${APR_VERSION} +APR_UTIL=apr-util-${APU_VERSION} +SERF=serf-${SERF_VERSION} +ZLIB=zlib-${ZLIB_VERSION} +SQLITE_VERSION_LIST=`echo $SQLITE_VERSION | sed -e 's/\./ /g'` +SQLITE=sqlite-amalgamation-`printf %d%02d%02d%02d $SQLITE_VERSION_LIST` +GTEST=gtest-${GTEST_VERSION} +GTEST_URL=http://googletest.googlecode.com/files/ + +HTTPD=httpd-${HTTPD_VERSION} +APR_ICONV=apr-iconv-${APR_ICONV_VERSION} + +BASEDIR=`pwd` +TEMPDIR=$BASEDIR/temp + +HTTP_FETCH= +[ -z "$HTTP_FETCH" ] && type wget >/dev/null 2>&1 && HTTP_FETCH="wget -q -nc" +[ -z "$HTTP_FETCH" ] && type curl >/dev/null 2>&1 && HTTP_FETCH="curl -sO" +[ -z "$HTTP_FETCH" ] && type fetch >/dev/null 2>&1 && HTTP_FETCH="fetch -q" + +# Need this uncommented if any of the specific versions of the ASF tarballs to +# be downloaded are no longer available on the general mirrors. +APACHE_MIRROR=http://archive.apache.org/dist + +# helpers +usage() { + echo "Usage: $0" + echo "Usage: $0 [ apr | serf | zlib | sqlite | gtest ] ..." + exit $1 +} + +# getters +get_apr() { + cd $TEMPDIR + test -d $BASEDIR/apr || $HTTP_FETCH $APACHE_MIRROR/apr/$APR.tar.bz2 + test -d $BASEDIR/apr-util || $HTTP_FETCH $APACHE_MIRROR/apr/$APR_UTIL.tar.bz2 + cd $BASEDIR + + test -d $BASEDIR/apr || bzip2 -dc $TEMPDIR/$APR.tar.bz2 | tar -xf - + test -d $BASEDIR/apr-util || bzip2 -dc $TEMPDIR/$APR_UTIL.tar.bz2 | tar -xf - + + test -d $BASEDIR/apr || mv $APR apr + test -d $BASEDIR/apr-util || mv $APR_UTIL apr-util +} + +get_serf() { + test -d $BASEDIR/serf && return + + cd $TEMPDIR + $HTTP_FETCH http://serf.googlecode.com/files/$SERF.tar.bz2 + cd $BASEDIR + + bzip2 -dc $TEMPDIR/$SERF.tar.bz2 | tar -xf - + + mv $SERF serf +} + +get_zlib() { + test -d $BASEDIR/zlib && return + + cd $TEMPDIR + $HTTP_FETCH http://www.zlib.net/$ZLIB.tar.gz + cd $BASEDIR + + gzip -dc $TEMPDIR/$ZLIB.tar.gz | tar -xf - + + mv $ZLIB zlib +} + +get_sqlite() { + test -d $BASEDIR/sqlite-amalgamation && return + + cd $TEMPDIR + $HTTP_FETCH http://www.sqlite.org/$SQLITE.zip + cd $BASEDIR + + unzip -q $TEMPDIR/$SQLITE.zip + + mv $SQLITE sqlite-amalgamation + +} + +get_gtest() { + test -d $BASEDIR/gtest && return + + cd $TEMPDIR + $HTTP_FETCH ${GTEST_URL}/${GTEST}.zip + cd $BASEDIR + + unzip -q $TEMPDIR/$GTEST.zip + + mv $GTEST gtest +} + +# main() +get_deps() { + mkdir -p $TEMPDIR + + for i in zlib serf sqlite-amalgamation apr apr-util gtest; do + if [ -d $i ]; then + echo "Local directory '$i' already exists; the downloaded copy won't be used" >&2 + fi + done + + if [ $# -gt 0 ]; then + for target in "$@"; do + if [ "$target" != "deps" ]; then + get_$target || usage + else + usage + fi + done + else + get_apr + get_serf + get_zlib + get_sqlite + + echo + echo "If you require mod_dav_svn, the recommended version of httpd is:" + echo " $APACHE_MIRROR/httpd/$HTTPD.tar.bz2" + + echo + echo "If you require apr-iconv, its recommended version is:" + echo " $APACHE_MIRROR/apr/$APR_ICONV.tar.bz2" + fi + + rm -rf $TEMPDIR +} + +get_deps "$@" diff --git a/subversion/include/mod_authz_svn.h b/subversion/include/mod_authz_svn.h new file mode 100644 index 000000000000..2cf1464cb4a5 --- /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 000000000000..c498c5e974a9 --- /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 000000000000..05527e288b20 --- /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 000000000000..428e20e8033c --- /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 000000000000..8c9bbd2774b5 --- /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 000000000000..187703b05455 --- /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 000000000000..7a1c7167b36a --- /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 000000000000..df40f7e0691f --- /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 000000000000..9eebc184c187 --- /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 000000000000..ad16b66cd6e3 --- /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 000000000000..94cf06930f33 --- /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 000000000000..a596ba15c367 --- /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 000000000000..4de85a94b6c9 --- /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 000000000000..71c0b9a75493 --- /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 000000000000..3b6e59133ec1 --- /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 000000000000..597a59be8394 --- /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 000000000000..426e3f7706e9 --- /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 000000000000..d714bb17a00d --- /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 000000000000..d2cce5cc770f --- /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 000000000000..f8bd2bc51617 --- /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 000000000000..20b70cd2059d --- /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 000000000000..eb0f024de847 --- /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 000000000000..01679b9f88dc --- /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 000000000000..2fb4fa5b35ea --- /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 000000000000..bdcf32f5a609 --- /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 000000000000..b057e563b3e8 --- /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 000000000000..287515a4e573 --- /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 000000000000..85583d32fbb3 --- /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 000000000000..4efa255e7583 --- /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 000000000000..6ae67a5bfb32 --- /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 000000000000..34d59296e8fe --- /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 000000000000..4531bcb00a41 --- /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 000000000000..b4294d048f0e --- /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 000000000000..8e943aeb1256 --- /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 000000000000..5b17b21dae79 --- /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 000000000000..c1d640b76c99 --- /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 000000000000..8579f4d3398f --- /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 000000000000..a45e6644a6ff --- /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 000000000000..7a007c38fbb3 --- /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 000000000000..c7c1c2cc8e64 --- /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 000000000000..9f5a4ad41ea2 --- /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 000000000000..fce42b053d33 --- /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 000000000000..dadc1cf6c6b5 --- /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 000000000000..cc1820fb93cf --- /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 000000000000..b03a07914993 --- /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 000000000000..d3271f5538b2 --- /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 000000000000..d8eacdf26786 --- /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 000000000000..80442e454a26 --- /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 000000000000..a127125c7d2d --- /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 000000000000..c5d697627858 --- /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 000000000000..126355292c60 --- /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 000000000000..e9092d541938 --- /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 000000000000..7df7f3f5fed7 --- /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 000000000000..23b8970fb1dd --- /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 000000000000..8fb449ded4c3 --- /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 000000000000..f5cfa107a136 --- /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 000000000000..3a6e4c5ac2ac --- /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 000000000000..222bc2bbbeef --- /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 000000000000..8cef9a3e2a53 --- /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 000000000000..46b4760068bf --- /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 000000000000..92874e1c1ed7 --- /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 000000000000..ab889358a20d --- /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 000000000000..e6e330df6a81 --- /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 000000000000..ada70a200964 --- /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 000000000000..6a5bc1b51202 --- /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 000000000000..25da44fd0813 --- /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 000000000000..347800391987 --- /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 000000000000..d4c3a53f1570 --- /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 000000000000..1f2bbbf0eb98 --- /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 000000000000..abbbe171c108 --- /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 000000000000..cf6f6129aa0f --- /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 000000000000..9c556b8b87e7 --- /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 000000000000..2cec6dd02ad9 --- /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 000000000000..b9e05e5257b1 --- /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 000000000000..d8ce02b039fd --- /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 000000000000..98aaf3e019d9 --- /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 000000000000..76517ca79c37 --- /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 000000000000..6b3a08741fa6 --- /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 000000000000..65e28200b6d0 --- /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 000000000000..4a2c137b8e64 --- /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 000000000000..f8ce7c37b3b3 --- /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 000000000000..2a9741d3398a --- /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 000000000000..90969be7ecde --- /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 000000000000..48dfa35be8d9 --- /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 000000000000..361fe016e163 --- /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 000000000000..e1a345e7d45c --- /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 000000000000..0a46e413685e --- /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 000000000000..f121bc87d17f --- /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 000000000000..188fdd2bdc2a --- /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 000000000000..7c58f884880e --- /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 000000000000..fc4d9870b956 --- /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 000000000000..41be776f16e2 --- /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 000000000000..b15e824d3490 --- /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 000000000000..9ea25f270398 --- /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 000000000000..a17f8c4783e5 --- /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 000000000000..3f6bfef2030a --- /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 000000000000..1e2c50cd9e61 --- /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 000000000000..ae53a15cdea8 --- /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 000000000000..c0501b952a04 --- /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 000000000000..8de8a5d6a4b4 --- /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 000000000000..185b91e8c6e3 --- /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 000000000000..2f4ee664f335 --- /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 000000000000..a67a69bb948a --- /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 000000000000..a5a36bd184a9 --- /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 000000000000..cc7184f12cba --- /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 000000000000..df0911baf284 --- /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 000000000000..d6022ed68f29 --- /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 000000000000..30748ea6af6c --- /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 000000000000..43e0d79d202a --- /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 000000000000..f49f22e8ee3e --- /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 000000000000..653ce8cfb99a --- /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 000000000000..4093893f0536 --- /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 000000000000..c768503ef968 --- /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 000000000000..ca3edac4becb --- /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 000000000000..884d63d57e8e --- /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 000000000000..453cc66a7772 --- /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 000000000000..0c4cf05d56e6 --- /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 000000000000..b965646f985a --- /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 000000000000..c3c1cfaa6aca --- /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 000000000000..33d3de509d68 --- /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 000000000000..ed8d09cc039b --- /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 000000000000..6a7725f59ff1 --- /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 000000000000..049637109dbc --- /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 000000000000..681e39c07676 --- /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 000000000000..ec255c1e1ca4 --- /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 000000000000..e581d37a41e4 --- /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 000000000000..fae03de09753 --- /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 000000000000..21f33ec93dc7 --- /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 000000000000..b9f323533ba4 --- /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 000000000000..36019adb3291 --- /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 000000000000..5ac0b8ffa11e --- /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 000000000000..2ad641738bc0 --- /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 000000000000..89995a85ebb6 --- /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 000000000000..8d315d194d57 --- /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 000000000000..7b96438f7e38 --- /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 000000000000..7c2cdec8eebe --- /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 000000000000..2b031afa22a1 --- /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 000000000000..2f1c9734ba6a --- /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 000000000000..95fe4f717afd --- /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 000000000000..41712440baf4 --- /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 000000000000..b16197923799 --- /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 000000000000..1dc94b2316d2 --- /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 000000000000..62e703a4642a --- /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 000000000000..f4b9dc686668 --- /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 000000000000..be2c434e8e3a --- /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 000000000000..ffbfb0f5553c --- /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 000000000000..2075a512bd29 --- /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 000000000000..891ad5fa8fc0 --- /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 000000000000..f43a3be405e2 --- /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 000000000000..51a84c640580 --- /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 000000000000..8b7c9b332817 --- /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 000000000000..9f3cb8cd2c93 --- /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 000000000000..e70c2f98a15d --- /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 000000000000..00f4c7fd4184 --- /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 000000000000..8490179c48e7 --- /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 000000000000..8087a92f5c01 --- /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 000000000000..a01b4d52743b --- /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 000000000000..6388d9f070e8 --- /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 000000000000..9e1f41176e54 --- /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 000000000000..9918be4c4f7f --- /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 000000000000..a75f21043c1c --- /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 000000000000..01d6ba1afc12 --- /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 000000000000..532ff0511042 --- /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 000000000000..3d5171164d3f --- /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 000000000000..200afe9b102e --- /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 000000000000..5596eee0a7d6 --- /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 000000000000..bea62de5f07a --- /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 000000000000..80ff468074e8 --- /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 000000000000..c9df6363cb01 --- /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 000000000000..f4a34c32610e --- /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 000000000000..ccdcd48636b3 --- /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 000000000000..7bf6ca8d70e8 --- /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 000000000000..08ab1393f8f0 --- /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 000000000000..a18ba471df06 --- /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 000000000000..db93d77b138a --- /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 000000000000..557c9dc36bb2 --- /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 000000000000..a8cce4e26ba7 --- /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 000000000000..e70ef170886d --- /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 000000000000..de958ced04be --- /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 000000000000..a22663f391e4 --- /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 000000000000..e0d087cdfe2d --- /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 000000000000..21a05ca857a5 --- /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 000000000000..f972d02b0905 --- /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 000000000000..48dc43b1a7dd --- /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 000000000000..44587caece8a --- /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 000000000000..d0f475fe716c --- /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 000000000000..c1687ab664aa --- /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 000000000000..1c8ea6de0d71 --- /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 000000000000..b5cd27d00d09 --- /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 000000000000..b752249bfa78 --- /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 000000000000..47209a882c22 --- /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 000000000000..f5348e7c1cb2 --- /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 000000000000..443cb72ffdac --- /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 000000000000..54a0e282d63d --- /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 000000000000..ff0cc9cb8e4e --- /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 000000000000..048189413727 --- /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 000000000000..f6d38df04fc8 --- /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 000000000000..510ccbbc7cc0 --- /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 000000000000..4c50c8441299 --- /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 000000000000..c1e691d34949 --- /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 000000000000..5c03d9fbee93 --- /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 000000000000..bee921ba6b2f --- /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 000000000000..017c89856f40 --- /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 000000000000..c063d02e5e6b --- /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 000000000000..4cdb45c92b45 --- /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 000000000000..411207d50016 --- /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 000000000000..e1cd1aa049ab --- /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 000000000000..79f72ccf7aee --- /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 000000000000..603e78c5a0f5 --- /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 000000000000..949a24b7d40a --- /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 000000000000..206be20c562e --- /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 000000000000..c72af03f6812 --- /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 000000000000..e4f51a682146 --- /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 000000000000..8e2159f29e1b --- /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 000000000000..553075d4eaef --- /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 000000000000..475af0c49409 --- /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 000000000000..d21884311a83 --- /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 000000000000..558a90c89266 --- /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 000000000000..8fdf9bea42b7 --- /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 000000000000..87fc313d1b4b --- /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 000000000000..e603af41e778 --- /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 000000000000..2e81a177cd23 --- /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 000000000000..f3466b95e1dc --- /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 000000000000..63dab8013a58 --- /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 000000000000..c865df38d8cf --- /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 000000000000..453a390f33f7 --- /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 000000000000..4af48b85807c --- /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 000000000000..3c51ffd7bc40 --- /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 000000000000..867b025f1723 --- /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 000000000000..4f3a340240f0 --- /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 000000000000..ea301f6f5e7e --- /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 000000000000..0354a1f5ffe9 --- /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 000000000000..c09f861f4aeb --- /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 000000000000..131782969fa9 --- /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 000000000000..11da4662752f --- /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 000000000000..a65c59d81689 --- /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 000000000000..e1b3858c9616 --- /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 000000000000..95bd94309852 --- /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 000000000000..1acc79e0b236 --- /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 000000000000..ed379a1536d1 --- /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 000000000000..b88c3e0c8d46 --- /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 000000000000..3a9469036187 --- /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 000000000000..3ccb05684744 --- /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 000000000000..41caf1d7b9c4 --- /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 "