# # add_file "tests/t_merge_binary.at" # # patch "ChangeLog" # from [3f11efe232c0f6d25bc099e5d444e9dc7713b75f] # to [ea2b89d5b00d53f64a81273c24c3dfbf87f1ff10] # # patch "Makefile.am" # from [c580427f04e6c7e40ed14b6a239f725f21c84c02] # to [a6c031cdb289e4294a118c97105a04e75fdebcb0] # # patch "database.cc" # from [f169cc616adacd9e668b8e89ee40f26c49f46d6d] # to [227d05b851a171bf13c1fa3899e2183d13e9678e] # # patch "database.hh" # from [77ee0f199a83b2174e046d007ee563ece0dc81d0] # to [5689e4a21edef9cc051cade4cc56518d492dfffd] # # patch "schema_migration.cc" # from [f9205058568c4e8b4b496d8b655a870f05edf0be] # to [06a7e2c801bb9d6e789f3883fa781a394993250a] # # patch "tests/t_merge_binary.at" # from [] # to [4cdc96fa85206b0b6d7488886045f1d541a55131] # # patch "tests/t_update_off_branch.at" # from [1ce8c8ecbb87f94254f0c7090d738273f4812b44] # to [00ba1adec31e5d98436ef58ea31579df603396a9] # --- ChangeLog +++ ChangeLog @@ -236,6 +236,242 @@ * win32/monotone.iss: Bump version number. +2005-07-13 Derek Scherger + + * database.cc (debug): delete stale comment + (delete_branch_named): + (delete_tag_named): + (clear): replace vprintf stuff with query parameters + +2005-07-13 Nathaniel Smith + + * monotone.texi (Database): Document kill_tag_locally. + +2005-07-13 Nathaniel Smith + + * tests/t_kill_tag_locally.at, tests/t_ambiguous_tags.at: New + tests. + * testsuite.at: Add them. + +2005-07-11 graydon hoare + + * AUTHORS: Add Jordan. + * commands.cc (ls_tags): Do not uniquify tags. + * constants.{cc,hh} (cvs_window): Change to time_t, tighten to 5 minutes. + * rcs_import.cc (window): Remove. + (note_type): Remove dead code. + (is_sbr): Add test for synthetic branch roots. + (cvs_commit::is_synthetic_branch_root): New test. + (process_branch): Skip synthetic branch roots, push new branch + before picking branch to mark, rather than after. + (cvs_history::index_branchpoint_symbols): Handle vendor branches. + (cvs_history::push_branch): Do not duplicate root on private branches. + (import_branch): Fix up cluster inference. + (cluster_consumer::consume_cluster): New invariant. + * tests/t_cvsimport_drepper2.at: Modify to reflect fixes. + +2005-07-11 Jordan Breeding + + * commands.cc (db): New subcommand "kill_tag_locally" + * database.{cc,hh} (delete_tag_named): New function. + +2005-07-12 Nathaniel Smith + + * schema_migration.cc (migrator::migrate): When there is nothing + to be done, do nothing. + +2005-07-12 Nathaniel Smith + + * netsync.cc (rebuild_merkle_trees): Reduce memory usage a bit, + and don't insert branch certs that the other side will just end up + throwing away (reduces network traffic). + +2005-07-12 Nathaniel Smith + + * testsuite.at (NETSYNC_SERVE_START, NETSYNC_SERVE_N_START): + Really, really really fix up quoting. Really. + I hope. + +2005-07-12 Nathaniel Smith + + * contrib/ciabot_monotone.py (config.project_for_branch): Clarify + comment text for non-Python programmers. + +2005-07-12 Nathaniel Smith + + * testsuite.at (NETSYNC_SERVE_START, NETSYNC_SERVE_N_START): Fixup + quoting. + +2005-07-11 Nathaniel Smith + + * crypto_tests.cc: New SHA1 correctness tests from Kaushik Veeraraghavan. + * unit_tests.cc (init_unit_test_suite): + * unit_tests.hh (add_crypto_tests): + * Makefile.am (unit_tests_SOURCES): Call them. + * AUTHORS: Add Kaushik Veeraraghavan. + +2005-07-11 Nathaniel Smith + + * tests/t_netsync_exclude_default.at: New test. + * testsuite.at: Add it. + (NETSYNC_SERVE_N_START, NETSYNC_SERVE_START): Use '*' as pattern + when none is passed. + +2005-07-11 Nathaniel Smith + + * monotone.texi (Network): Tweak documentation for netsync + commands. + +2005-07-11 Nathaniel Smith + + * app_state.{hh,cc} (exclude_patterns, add_exclude): + * options.hh (OPT_EXCLUDE): + * monotone.cc (coptions, cpp_main): New option --exclude. + * commands.cc (pull, push, sync, serve): Accept it. + (process_netsync_args): Implement it. + * tests/t_netsync_exclude.at: New test. + * testsuite.at: Add it. + +2005-07-11 Timothy Brownawell + + * options.hh, app_state.{hh,cc}, monotone.cc: New command specific + option, "--exclude=x", puts arg into a vector app.excludes . + Used by the netsync commands. + * commands.cc (netsync commands): accept said option + (process_netsync_args): Handle excludes. + * monotone.texi: document it + +2005-07-11 Timothy Brownawell + + * interner.hh: make slightly faster + +2005-07-10 Nathaniel Smith + + * ChangeLog, configure.ac: Re-remove mysteriously revived + jibberish. + +2005-07-10 Nathaniel Smith + + * tests/t_netsync_read_permissions.at: New test. + * testsuite.at: Run it. + * netsync.cc (set_session_key, dispatch_payload) + (respond_to_auth_cmd): Refactor to key HMAC earlier, so error + packets will get the right HMAC. + +2005-07-10 Richard Levitte + + * Makefile.am (monotone_CPPFLAGS, unit_tests_CPPFLAGS): Re-remove + previously removed stuff. + + * ChangeLog, configure.ac: Revert accidentally-recommitted changes. + +2005-07-10 Richard Levitte + + * monotone.texi (Network), monotone.1: Mention the default port + number. + +2005-07-10 Matthew Gregan + + * configure.ac: Check for boost >= 1.32. + +2005-07-09 Nathaniel Smith + + * schema.sql (revision_ancestry__child, revision_certs__id, + revision_certs__name_value): New indexes. + * database.cc (dump, dump_table_cb, dump_index_cb): Include + indexes in dumps. + (database::database): + * schema_migration.cc (migrate_monotone_schema) + (migrate_client_to_add_indexes): + * tests/t_migrate_schema.at: Corresponding migration gunk. + +2005-07-09 Jordan Breeding + + * Makefile.am (monotone_CPPFLAGS, unit_tests_CPPFLAGS): + * configure.ac (BOOST_FIX_VERSION): Restrict boost compile kluges + to boost 1.32. + +2005-07-09 Nathaniel Smith + + * schema_migration.cc (calculate_schema_id): Include indexes in + the schema id. + +2005-07-09 Nathaniel Smith + + * ChangeLog, configure.ac: Revert accidentally-committed changes. + +2005-07-09 Nathaniel Smith + + * monotone.texi (Generating Keys): Make it a little clearer that + we aren't necessarily recommending people store their passphrase + in plaintext. + +2005-07-08 Matt Johnston + + * tests/t_normalized_filenames.at: expect exit code of 1 not 3 for + "cat manifest" with a directory in MT/work + * file_io.cc, netcmd.cc, transforms.cc, vocab.hh: revert changes which + used swap() for strings and atomic types since strings are + copy-on-write. + +2005-07-08 Matt Johnston + + * file_io.cc (ident_existing_file): new function to calculate + the ident of a file failing gracefully if it doesn't exist + or is a directory. + * file_io.hh (classify_manifest_paths, + build_restricted_manifest_map): use ident_existing_file + * ui.cc: cast to avoid compiler warnings + +2005-07-07 Nathaniel Smith + + * contrib/ciabot_monotone.py (Monotone.log): Fix to work with + 0.20. + +2005-07-07 Nathaniel Smith + + * Makefile.am (monotone_CPPFLAGS, unit_tests_CPPFLAGS): Add + -DBOOST_REGEX_V4_CHAR_REGEX_TRAITS_HPP to work around g++ + 4.0/boost 1.32.0 lossage. + +2005-07-07 Vaclav Haisman + + * Makefile.am: Compile fix for FreeBSD. + +2005-07-07 Nathaniel Smith + + * netsync.cc (process_hello_cmd, process_anonymous_cmd) + (process_auth_cmd): Change permission checking -- always build + merkle tree (even when a pure sink), send permission denied and + abort whenever client tries to read/write a branch they don't have + access to. + +2005-07-07 Nathaniel Smith + + * ChangeLog: fixup formatting. + +2005-07-06 Matt Johnston + + * database.cc (assert_sqlite3_ok): database corruption and similar + problems are errors, not invariants. + +2005-07-06 Nathaniel Smith + + * commands.cc (push, pull, sync): Fix --help description. + +2005-07-06 Nathaniel Smith + + * options.hh (OPT_SET_DEFAULT): + * app_state.{hh,cc} (app_state::set_default): + * monotone.cc (coptions, cpp_main): New option. + * commands.cc (pull, push, sync): Accept it. + (process_netsync_args): Use it. + * tests/t_set_default.at, testsuite.at: New test. + +2005-07-07 Matthew Gregan + + * win32/monotone.iss: Bump version number. + 2005-07-05 Nathaniel Smith * debian/rules (config.status): Use bundled sqlite. @@ -5287,6 +5523,10 @@ * tests/t_vcheck.at: New priority "todo", tweak descriptive text. +2005-01-23 Derek Scherger + + * database.{cc,hh}: convert queries to use prepared statements + 2005-01-22 Nathaniel Smith * tests/t_delete_dir.at: Add more commentary. --- Makefile.am +++ Makefile.am @@ -287,6 +287,7 @@ $(wildcard $(srcdir)/popt/test?.c) popt/testit.sh \ sqlite/keywordhash.h \ contrib/README \ + contrib/monoprof.sh \ contrib/monotone-notify.pl \ contrib/monotone-import.pl \ contrib/ciabot_monotone.py \ --- database.cc +++ database.cc @@ -58,59 +58,10 @@ extern "C" { // some wrappers to ease migration - int sqlite3_exec_printf(sqlite3*,const char *sqlFormat,sqlite3_callback, - void *,char **errmsg,...); - int sqlite3_exec_vprintf(sqlite3*,const char *sqlFormat,sqlite3_callback, - void *,char **errmsg,va_list ap); - int sqlite3_get_table_vprintf(sqlite3*,const char *sqlFormat,char ***resultp, - int *nrow,int *ncolumn,char **errmsg,va_list ap); const char *sqlite3_value_text_s(sqlite3_value *v); + const char *sqlite3_column_text_s(sqlite3_stmt*, int col); } -int sqlite3_exec_printf(sqlite3 * db, - char const * sqlFormat, - sqlite3_callback cb, - void * user_data, - char ** errmsg, - ...) -{ - va_list ap; - va_start(ap, errmsg); - int result = sqlite3_exec_vprintf(db, sqlFormat, cb, - user_data, errmsg, ap); - va_end(ap); - return result; -} - -int sqlite3_exec_vprintf(sqlite3 * db, - char const * sqlFormat, - sqlite3_callback cb, - void * user_data, - char ** errmsg, - va_list ap) -{ - char * formatted = sqlite3_vmprintf(sqlFormat, ap); - int result = sqlite3_exec(db, formatted, cb, - user_data, errmsg); - sqlite3_free(formatted); - return result; -} - -int sqlite3_get_table_vprintf(sqlite3 * db, - char const * sqlFormat, - char *** resultp, - int * nrow, - int * ncolumn, - char ** errmsg, - va_list ap) -{ - char * formatted = sqlite3_vmprintf(sqlFormat, ap); - int result = sqlite3_get_table(db, formatted, resultp, - nrow, ncolumn, errmsg); - sqlite3_free(formatted); - return result; -} - database::database(fs::path const & fn) : filename(fn), // nb. update this if you change the schema. unfortunately we are not @@ -142,6 +93,12 @@ return (const char *)(sqlite3_value_text(v)); } +const char * +sqlite3_column_text_s(sqlite3_stmt *stmt, int col) +{ + return (const char *)(sqlite3_column_text(stmt, col)); +} + static void sqlite3_unbase64_fn(sqlite3_context *f, int nargs, sqlite3_value ** args) { @@ -201,6 +158,18 @@ } +static void +assert_sqlite3_ok(sqlite3 *s) +{ + int errcode = sqlite3_errcode(s); + + if (errcode == SQLITE_OK) return; + + const char * errmsg = sqlite3_errmsg(s); + + E(errcode == SQLITE_OK, F("sqlite error [%d]: %s") % errcode % errmsg); +} + struct sqlite3 * database::sql(bool init) { @@ -223,7 +192,10 @@ throw oops(string("could not open database: ") + filename.string() + (": " + string(sqlite3_errmsg(__sql)))); if (init) - execute(schema_constant); + { + sqlite3_exec(__sql, schema_constant, NULL, NULL, NULL); + assert_sqlite3_ok(__sql); + } check_schema(); install_functions(__app); @@ -308,8 +280,9 @@ I(string(vals[1]) == "table"); *(dump->out) << vals[2] << ";\n"; dump->table_name = string(vals[0]); - sqlite3_exec_printf(dump->sql, "SELECT * FROM '%q'", - dump_row_cb, data, NULL, vals[0]); + string query = "SELECT * FROM " + string(vals[0]); + sqlite3_exec(dump->sql, query.c_str(), dump_row_cb, data, NULL); + assert_sqlite3_ok(dump->sql); return 0; } @@ -342,13 +315,13 @@ "WHERE type='table' AND sql NOT NULL " "ORDER BY name", dump_table_cb, &req, NULL); - I(res == SQLITE_OK); + assert_sqlite3_ok(req.sql); res = sqlite3_exec(req.sql, "SELECT name, type, sql FROM sqlite_master " "WHERE type='index' AND sql NOT NULL " "ORDER BY name", dump_index_cb, &req, NULL); - I(res == SQLITE_OK); + assert_sqlite3_ok(req.sql); out << "COMMIT;\n"; } @@ -377,11 +350,12 @@ if (last_statement == 0) continue; string::size_type len = last_statement + 1 - tmp.c_str(); - execute(tmp.substr(0, len).c_str()); + sqlite3_exec(__sql, tmp.substr(0, len).c_str(), NULL, NULL, NULL); tmp.erase(0, len); } - execute(tmp.c_str()); + sqlite3_exec(__sql, tmp.c_str(), NULL, NULL, NULL); + assert_sqlite3_ok(__sql); } @@ -389,9 +363,7 @@ database::debug(string const & sql, ostream & out) { results res; - // "%s" construction prevents interpretation of %-signs in the query string - // as formatting commands. - fetch(res, any_cols, any_rows, "%s", sql.c_str()); + fetch(res, any_cols, any_rows, sql.c_str()); out << "'" << sql << "' -> " << res.size() << " rows\n" << endl; for (size_t i = 0; i < res.size(); ++i) { @@ -523,7 +495,7 @@ { hexenc tmp; key_hash_code(rsa_keypair_id(res[i][0]), base64(res[i][1]), tmp); - execute("INSERT INTO public_keys VALUES('%q', '%q', '%q')", + execute("INSERT INTO public_keys VALUES(?, ?, ?)", tmp().c_str(), res[i][0].c_str(), res[i][1].c_str()); ++pubkeys; } @@ -538,7 +510,7 @@ { hexenc tmp; key_hash_code(rsa_keypair_id(res[i][0]), base64< arc4 >(res[i][1]), tmp); - execute("INSERT INTO private_keys VALUES('%q', '%q', '%q')", + execute("INSERT INTO private_keys VALUES(?, ?, ?)", tmp().c_str(), res[i][0].c_str(), res[i][1].c_str()); ++privkeys; } @@ -556,6 +528,16 @@ database::~database() { + L(F("statement cache statistics\n")); + L(F("prepared %d statements\n") % statement_cache.size()); + + for (map::const_iterator i = statement_cache.begin(); + i != statement_cache.end(); ++i) + { + L(F("%d executions of %s\n") % i->second.count % i->first); + sqlite3_finalize(i->second.stmt); + } + if (__sql) { sqlite3_close(__sql); @@ -711,111 +693,114 @@ void database::execute(char const * query, ...) { - va_list ap; + results res; + va_list args; + va_start(args, query); + fetch(res, 0, 0, query, args); + va_end(args); +} - va_start(ap, query); - - // log it - char * formatted = sqlite3_vmprintf(query, ap); - string qq; - - if (strlen(formatted) > constants::db_log_line_sz) - { - qq.assign(formatted, constants::db_log_line_sz); - qq.append(" ..."); - } - else - { - qq = formatted; - } - L(F("db.execute(\"%s\")\n") % qq); - - // do it - char * errmsg = NULL; - int res = sqlite3_exec(sql(), formatted, NULL, NULL, &errmsg); - sqlite3_free(formatted); - - va_end(ap); - - assert_sqlite3_ok(res, errmsg); - +void +database::fetch(results & res, + int const want_cols, + int const want_rows, + char const * query, ...) +{ + va_list args; + va_start(args, query); + fetch(res, want_cols, want_rows, query, args); + va_end(args); } void database::fetch(results & res, int const want_cols, int const want_rows, - char const * query, ...) + char const * query, + va_list args) { - char ** result = NULL; int nrow; int ncol; - char * errmsg = NULL; int rescode; - va_list ap; res.clear(); res.resize(0); - va_start(ap, query); - // log it - char * formatted = sqlite3_vmprintf(query, ap); - string qq(formatted); - if (qq.size() > constants::log_line_sz) - qq = qq.substr(0, constants::log_line_sz) + string(" ..."); - L(F("db.fetch(\"%s\")\n") % qq); - sqlite3_free(formatted); + map::iterator i = statement_cache.find(query); + if (i == statement_cache.end()) + { + statement_cache.insert(make_pair(query, statement())); + i = statement_cache.find(query); + I(i != statement_cache.end()); - va_end(ap); - va_start(ap, query); + const char * tail; + sqlite3_prepare(sql(), query, -1, &i->second.stmt, &tail); + assert_sqlite3_ok(sql()); + L(F("prepared statement %s\n") % query); - // do it - rescode = sqlite3_get_table_vprintf(sql(), query, &result, &nrow, &ncol, &errmsg, ap); + // no support for multiple statements here + E(*tail == 0, + F("multiple statements in query: %s\n") % query); + } - va_end(ap); + ncol = sqlite3_column_count(i->second.stmt); - cleanup_ptr - result_guard(result, &sqlite3_free_table); + E(want_cols == any_cols || want_cols == ncol, + F("wanted %d columns got %d in query: %s\n") % want_cols % ncol % query); - string ctx = string("db query [") + string(query) + "]: "; + // bind parameters for this execution - assert_sqlite3_ok(rescode, errmsg, ctx); + int params = sqlite3_bind_parameter_count(i->second.stmt); - if (want_cols == 0 && ncol == 0) return; - if (want_rows == 0 && nrow == 0) return; - if (want_cols == any_rows && ncol == 0) return; - if (want_rows == any_rows && nrow == 0) return; + L(F("binding %d parameters for %s\n") % params % query); - if (want_cols != any_cols && - ncol != want_cols) - throw oops((F("%s wanted %d columns, got %s") - % ctx % want_cols % ncol).str()); + for (int param = 1; param <= params; param++) + { + char *value = va_arg(args, char *); + // nb: transient will not be good for inserts with large data blobs + // however, it's no worse than the previous '%q' stuff in this regard + // might want to wrap this logging with --debug or --verbose to limit it - if (want_rows != any_rows && - nrow != want_rows) - throw oops((F("%s wanted %d rows, got %s") - % ctx % want_rows % nrow).str()); + string log = string(value); - if (!result) - throw oops(ctx + "null result set"); + if (log.size() > constants::log_line_sz) + log = log.substr(0, constants::log_line_sz); - for (int i = 0; i < ncol; ++i) - if (!result[i]) - throw oops(ctx + "null column name"); + L(F("binding %d with value '%s'\n") % param % log); - for (int row = 0; row < nrow; ++row) + sqlite3_bind_text(i->second.stmt, param, value, -1, SQLITE_TRANSIENT); + assert_sqlite3_ok(sql()); + } + + // execute and process results + + nrow = 0; + for (rescode = sqlite3_step(i->second.stmt); rescode == SQLITE_ROW; + rescode = sqlite3_step(i->second.stmt)) { - vector rowvec; - for (int col = 0; col < ncol; ++col) + vector row; + for (int col = 0; col < ncol; col++) { - int i = ((1 + row) * ncol) + col; - if (!result[i]) - throw oops(ctx + "null result value"); - else - rowvec.push_back(result[i]); + const char * value = sqlite3_column_text_s(i->second.stmt, col); + E(value, F("null result in query: %s\n") % query); + row.push_back(value); + //L(F("row %d col %d value='%s'\n") % nrow % col % value); } - res.push_back(rowvec); + res.push_back(row); } + + if (rescode != SQLITE_DONE) + assert_sqlite3_ok(sql()); + + sqlite3_reset(i->second.stmt); + assert_sqlite3_ok(sql()); + + nrow = res.size(); + + i->second.count++; + + E(want_rows == any_rows || want_rows == nrow, + F("wanted %d rows got %s in query: %s\n") % want_rows % nrow % query); } // general application-level logic @@ -834,7 +819,7 @@ database::begin_transaction() { if (transaction_level == 0) - execute("BEGIN"); + execute("BEGIN"); transaction_level++; } @@ -860,9 +845,8 @@ string const & table) { results res; - fetch(res, one_col, any_rows, - "SELECT id FROM '%q' WHERE id = '%q'", - table.c_str(), ident().c_str()); + string query = "SELECT id FROM " + table + " WHERE id = ?"; + fetch(res, one_col, any_rows, query.c_str(), ident().c_str()); I((res.size() == 1) || (res.size() == 0)); return res.size() == 1; } @@ -873,9 +857,8 @@ string const & table) { results res; - fetch(res, one_col, any_rows, - "SELECT id FROM '%q' WHERE id = '%q'", - table.c_str(), ident().c_str()); + string query = "SELECT id FROM " + table + " WHERE id = ?"; + fetch(res, one_col, any_rows, query.c_str(), ident().c_str()); return res.size() > 0; } @@ -885,9 +868,9 @@ string const & table) { results res; - fetch(res, one_col, any_rows, - "SELECT id FROM '%q' WHERE id = '%q' AND base = '%q'", - table.c_str(), ident().c_str(), base().c_str()); + string query = "SELECT id FROM " + table + " WHERE id = ? AND base = ?"; + fetch(res, one_col, any_rows, query.c_str(), + ident().c_str(), base().c_str()); I((res.size() == 1) || (res.size() == 0)); return res.size() == 1; } @@ -896,9 +879,8 @@ database::count(string const & table) { results res; - fetch(res, one_col, one_row, - "SELECT COUNT(*) FROM '%q'", - table.c_str()); + string query = "SELECT COUNT(*) FROM " + table; + fetch(res, one_col, one_row, query.c_str()); return lexical_cast(res[0][0]); } @@ -906,9 +888,8 @@ database::space_usage(string const & table, string const & concatenated_columns) { results res; - fetch(res, one_col, one_row, - "SELECT SUM(LENGTH(%s)) FROM '%q'", - concatenated_columns.c_str(), table.c_str()); + string query = "SELECT SUM(LENGTH(" + concatenated_columns + ")) FROM " + table; + fetch(res, one_col, one_row, query.c_str()); return lexical_cast(res[0][0]); } @@ -916,9 +897,9 @@ database::get_ids(string const & table, set< hexenc > & ids) { results res; + string query = "SELECT id FROM " + table; + fetch(res, one_col, any_rows, query.c_str()); - fetch(res, one_col, any_rows, "SELECT id FROM %q", table.c_str()); - for (size_t i = 0; i < res.size(); ++i) { ids.insert(hexenc(res[i][0])); @@ -931,9 +912,8 @@ string const & table) { results res; - fetch(res, one_col, one_row, - "SELECT data FROM '%q' WHERE id = '%q'", - table.c_str(), ident().c_str()); + string query = "SELECT data FROM " + table + " WHERE id = ?"; + fetch(res, one_col, one_row, query.c_str(), ident().c_str()); // consistency check base64 > rdata(res[0][0]); @@ -956,9 +936,9 @@ I(ident() != ""); I(base() != ""); results res; - fetch(res, one_col, one_row, - "SELECT delta FROM '%q' WHERE id = '%q' AND base = '%q'", - table.c_str(), ident().c_str(), base().c_str()); + string query = "SELECT delta FROM " + table + " WHERE id = ? AND base = ?"; + fetch(res, one_col, one_row, query.c_str(), + ident().c_str(), base().c_str()); base64 > del_packed = res[0][0]; unpack(del_packed, del); @@ -978,8 +958,8 @@ base64 > dat_packed; pack(dat, dat_packed); - execute("INSERT INTO '%q' VALUES('%q', '%q')", - table.c_str(), ident().c_str(), dat_packed().c_str()); + string insert = "INSERT INTO " + table + " VALUES(?, ?)"; + execute(insert.c_str(),ident().c_str(), dat_packed().c_str()); } void database::put_delta(hexenc const & ident, @@ -994,9 +974,8 @@ base64 > del_packed; pack(del, del_packed); - execute("INSERT INTO '%q' VALUES('%q', '%q', '%q')", - table.c_str(), - ident().c_str(), base().c_str(), del_packed().c_str()); + string insert = "INSERT INTO "+table+" VALUES(?, ?, ?)"; + execute(insert.c_str(), ident().c_str(), base().c_str(), del_packed().c_str()); } // static ticker cache_hits("vcache hits", "h", 1); @@ -1112,6 +1091,8 @@ bool found_root = false; hexenc root(""); + string delta_query = "SELECT base FROM " + delta_table + " WHERE id = ?"; + while (! found_root) { set< hexenc > next_frontier; @@ -1132,8 +1113,10 @@ { cycles.insert(*i); results res; - fetch(res, one_col, any_rows, "SELECT base from '%q' WHERE id = '%q'", - delta_table.c_str(), (*i)().c_str()); + + fetch(res, one_col, any_rows, + delta_query.c_str(), (*i)().c_str()); + for (size_t k = 0; k < res.size(); ++k) { hexenc const nxt(res[k][0]); @@ -1221,9 +1204,8 @@ database::drop(hexenc const & ident, string const & table) { - execute("DELETE FROM '%q' WHERE id = '%q'", - table.c_str(), - ident().c_str()); + string drop = "DELETE FROM " + table + " WHERE id = ?"; + execute(drop.c_str(), ident().c_str()); } void @@ -1430,7 +1412,7 @@ results res; parents.clear(); fetch(res, one_col, any_rows, - "SELECT parent FROM revision_ancestry WHERE child = '%q'", + "SELECT parent FROM revision_ancestry WHERE child = ?", id.inner()().c_str()); for (size_t i = 0; i < res.size(); ++i) parents.insert(revision_id(res[i][0])); @@ -1444,7 +1426,7 @@ results res; children.clear(); fetch(res, one_col, any_rows, - "SELECT child FROM revision_ancestry WHERE parent = '%q'", + "SELECT child FROM revision_ancestry WHERE parent = ?", id.inner()().c_str()); for (size_t i = 0; i < res.size(); ++i) children.insert(revision_id(res[i][0])); @@ -1475,7 +1457,7 @@ I(!null_id(id)); results res; fetch(res, one_col, one_row, - "SELECT data FROM revisions WHERE id = '%q'", + "SELECT data FROM revisions WHERE id = ?", id.inner()().c_str()); base64 > rdat_packed; @@ -1514,14 +1496,14 @@ transaction_guard guard(*this); - execute("INSERT INTO revisions VALUES('%q', '%q')", + execute("INSERT INTO revisions VALUES(?, ?)", new_id.inner()().c_str(), d_packed().c_str()); for (edge_map::const_iterator e = rev.edges.begin(); e != rev.edges.end(); ++e) { - execute("INSERT INTO revision_ancestry VALUES('%q', '%q')", + execute("INSERT INTO revision_ancestry VALUES(?, ?)", edge_old_revision(e).inner()().c_str(), new_id.inner()().c_str()); } @@ -1544,9 +1526,9 @@ void database::delete_existing_revs_and_certs() { - execute("DELETE from revisions"); - execute("DELETE from revision_ancestry"); - execute("DELETE from revision_certs"); + execute("DELETE FROM revisions"); + execute("DELETE FROM revision_ancestry"); + execute("DELETE FROM revision_certs"); } /// Deletes one revision from the local database. @@ -1562,10 +1544,9 @@ // perform the actual SQL transactions to kill rev rid here L(F("Killing revision %s locally\n") % rid); - execute("DELETE from revision_certs WHERE id = '%s'",rid.inner()().c_str()); - execute("DELETE from revision_ancestry WHERE child = '%s'", - rid.inner()().c_str()); - execute("DELETE from revisions WHERE id = '%s'",rid.inner()().c_str()); + execute("DELETE from revision_certs WHERE id = ?",rid.inner()().c_str()); + execute("DELETE from revision_ancestry WHERE child = ?", rid.inner()().c_str()); + execute("DELETE from revisions WHERE id = ?",rid.inner()().c_str()); } /// Deletes all certs referring to a particular branch. @@ -1575,9 +1556,9 @@ base64 encoded; encode_base64(branch, encoded); L(F("Deleting all references to branch %s\n") % branch); - execute("DELETE FROM revision_certs WHERE name='branch' AND value ='%s'", + execute("DELETE FROM revision_certs WHERE name='branch' AND value =?", encoded().c_str()); - execute("DELETE FROM branch_epochs WHERE branch='%s'", + execute("DELETE FROM branch_epochs WHERE branch=?", encoded().c_str()); } @@ -1588,7 +1569,7 @@ base64 encoded; encode_base64(tag, encoded); L(F("Deleting all references to tag %s\n") % tag); - execute("DELETE FROM revision_certs WHERE name='tag' AND value ='%s'", + execute("DELETE FROM revision_certs WHERE name='tag' AND value =?", encoded().c_str()); } @@ -1605,22 +1586,22 @@ if (pattern != "") fetch(res, one_col, any_rows, - "SELECT id from public_keys WHERE id GLOB '%q'", + "SELECT id FROM public_keys WHERE id GLOB ?", pattern.c_str()); else fetch(res, one_col, any_rows, - "SELECT id from public_keys"); + "SELECT id FROM public_keys"); for (size_t i = 0; i < res.size(); ++i) pubkeys.push_back(res[i][0]); if (pattern != "") fetch(res, one_col, any_rows, - "SELECT id from private_keys WHERE id GLOB '%q'", + "SELECT id FROM private_keys WHERE id GLOB ?", pattern.c_str()); else fetch(res, one_col, any_rows, - "SELECT id from private_keys"); + "SELECT id FROM private_keys"); for (size_t i = 0; i < res.size(); ++i) privkeys.push_back(res[i][0]); @@ -1631,7 +1612,8 @@ { keys.clear(); results res; - fetch(res, one_col, any_rows, "SELECT id from '%q'", table.c_str()); + string query = "SELECT id FROM " + table; + fetch(res, one_col, any_rows, query.c_str()); for (size_t i = 0; i < res.size(); ++i) keys.push_back(res[i][0]); } @@ -1653,7 +1635,7 @@ { results res; fetch(res, one_col, any_rows, - "SELECT id FROM public_keys WHERE hash = '%q'", + "SELECT id FROM public_keys WHERE hash = ?", hash().c_str()); I((res.size() == 1) || (res.size() == 0)); if (res.size() == 1) @@ -1666,7 +1648,7 @@ { results res; fetch(res, one_col, any_rows, - "SELECT id FROM public_keys WHERE id = '%q'", + "SELECT id FROM public_keys WHERE id = ?", id().c_str()); I((res.size() == 1) || (res.size() == 0)); if (res.size() == 1) @@ -1679,7 +1661,7 @@ { results res; fetch(res, one_col, any_rows, - "SELECT id FROM private_keys WHERE id = '%q'", + "SELECT id FROM private_keys WHERE id = ?", id().c_str()); I((res.size() == 1) || (res.size() == 0)); if (res.size() == 1) @@ -1700,7 +1682,7 @@ { results res; fetch(res, 2, one_row, - "SELECT id, keydata FROM public_keys where hash = '%q'", + "SELECT id, keydata FROM public_keys WHERE hash = ?", hash().c_str()); id = res[0][0]; pub_encoded = res[0][1]; @@ -1712,7 +1694,7 @@ { results res; fetch(res, one_col, one_row, - "SELECT keydata FROM public_keys where id = '%q'", + "SELECT keydata FROM public_keys WHERE id = ?", pub_id().c_str()); pub_encoded = res[0][0]; } @@ -1723,7 +1705,7 @@ { results res; fetch(res, one_col, one_col, - "SELECT keydata FROM private_keys where id = '%q'", + "SELECT keydata FROM private_keys WHERE id = ?", priv_id().c_str()); priv_encoded = res[0][0]; } @@ -1737,7 +1719,7 @@ I(!public_key_exists(thash)); E(!public_key_exists(pub_id), F("another key with name '%s' already exists") % pub_id); - execute("INSERT INTO public_keys VALUES('%q', '%q', '%q')", + execute("INSERT INTO public_keys VALUES(?, ?, ?)", thash().c_str(), pub_id().c_str(), pub_encoded().c_str()); } @@ -1749,7 +1731,7 @@ key_hash_code(priv_id, priv_encoded, thash); E(!private_key_exists(priv_id), F("another key with name '%s' already exists") % priv_id); - execute("INSERT INTO private_keys VALUES('%q', '%q', '%q')", + execute("INSERT INTO private_keys VALUES(?, ?, ?)", thash().c_str(), priv_id().c_str(), priv_encoded().c_str()); } @@ -1767,14 +1749,14 @@ void database::delete_private_key(rsa_keypair_id const & pub_id) { - execute("DELETE FROM private_keys WHERE id = '%q'", + execute("DELETE FROM private_keys WHERE id = ?", pub_id().c_str()); } void database::delete_public_key(rsa_keypair_id const & pub_id) { - execute("DELETE FROM public_keys WHERE id = '%q'", + execute("DELETE FROM public_keys WHERE id = ?", pub_id().c_str()); } @@ -1785,11 +1767,14 @@ string const & table) { results res; - fetch(res, 1, any_rows, - "SELECT id FROM '%q' WHERE id = '%q' " - "AND name = '%q' AND value = '%q' " - "AND keypair = '%q' AND signature = '%q' ", - table.c_str(), + string query = + "SELECT id FROM " + table + " WHERE id = ? " + "AND name = ? " + "AND value = ? " + "AND keypair = ? " + "AND signature = ?"; + + fetch(res, 1, any_rows, query.c_str(), t.ident().c_str(), t.name().c_str(), t.value().c_str(), @@ -1805,8 +1790,10 @@ { hexenc thash; cert_hash_code(t, thash); - execute("INSERT INTO '%q' VALUES('%q', '%q', '%q', '%q', '%q', '%q')", - table.c_str(), + + string insert = "INSERT INTO " + table + " VALUES(?, ?, ?, ?, ?, ?)"; + + execute(insert.c_str(), thash().c_str(), t.ident().c_str(), t.name().c_str(), @@ -1858,9 +1845,11 @@ results res; fetch(res, one_col, any_rows, "SELECT name FROM sqlite_master WHERE type='view'"); + for (size_t i = 0; i < res.size(); ++i) { - execute("DROP VIEW '%q'", res[i][0].c_str()); + string drop = "DROP VIEW " + res[i][0]; + execute(drop.c_str()); } // register any views we're going to use execute(views_constant); @@ -1872,9 +1861,8 @@ string const & table) { results res; - fetch(res, 5, any_rows, - "SELECT id, name, value, keypair, signature FROM '%q' ", - table.c_str()); + string query = "SELECT id, name, value, keypair, signature FROM " + table; + fetch(res, 5, any_rows, query.c_str()); results_to_certs(res, certs); } @@ -1885,11 +1873,11 @@ string const & table) { results res; - fetch(res, 5, any_rows, - "SELECT id, name, value, keypair, signature FROM '%q' " - "WHERE id = '%q'", - table.c_str(), - ident().c_str()); + string query = + "SELECT id, name, value, keypair, signature FROM " + table + + " WHERE id = ?"; + + fetch(res, 5, any_rows, query.c_str(), ident().c_str()); results_to_certs(res, certs); } @@ -1900,11 +1888,10 @@ string const & table) { results res; - fetch(res, 5, any_rows, - "SELECT id, name, value, keypair, signature " - "FROM '%q' WHERE name = '%q'", - table.c_str(), - name().c_str()); + string query = + "SELECT id, name, value, keypair, signature FROM " + table + + " WHERE name = ?"; + fetch(res, 5, any_rows, query.c_str(), name().c_str()); results_to_certs(res, certs); } @@ -1916,13 +1903,12 @@ string const & table) { results res; - fetch(res, 5, any_rows, - "SELECT id, name, value, keypair, signature " - "FROM '%q' " - "WHERE id = '%q' AND name = '%q'", - table.c_str(), - ident().c_str(), - name().c_str()); + string query = + "SELECT id, name, value, keypair, signature FROM " + table + + " WHERE id = ? AND name = ?"; + + fetch(res, 5, any_rows, query.c_str(), + ident().c_str(), name().c_str()); results_to_certs(res, certs); } @@ -1933,13 +1919,12 @@ string const & table) { results res; - fetch(res, 5, any_rows, - "SELECT id, name, value, keypair, signature " - "FROM '%q' " - "WHERE name = '%q' AND value = '%q'", - table.c_str(), - name().c_str(), - val().c_str()); + string query = + "SELECT id, name, value, keypair, signature FROM " + table + + " WHERE name = ? AND value = ?"; + + fetch(res, 5, any_rows, query.c_str(), + name().c_str(), val().c_str()); results_to_certs(res, certs); } @@ -1952,11 +1937,11 @@ string const & table) { results res; - fetch(res, 5, any_rows, - "SELECT id, name, value, keypair, signature " - "FROM '%q' " - "WHERE id = '%q' AND name = '%q' AND value = '%q'", - table.c_str(), + string query = + "SELECT id, name, value, keypair, signature FROM " + table + + " WHERE id = ? AND name = ? AND value = ?"; + + fetch(res, 5, any_rows, query.c_str(), ident().c_str(), name().c_str(), value().c_str()); @@ -2079,7 +2064,7 @@ fetch(res, 5, one_row, "SELECT id, name, value, keypair, signature " "FROM revision_certs " - "WHERE hash = '%q'", + "WHERE hash = ?", hash().c_str()); results_to_certs(res, certs); I(certs.size() == 1); @@ -2094,7 +2079,7 @@ fetch(res, one_col, any_rows, "SELECT id " "FROM revision_certs " - "WHERE hash = '%q'", + "WHERE hash = ?", hash().c_str()); I(res.size() == 0 || res.size() == 1); return (res.size() == 1); @@ -2108,7 +2093,7 @@ fetch(res, one_col, any_rows, "SELECT id " "FROM manifest_certs " - "WHERE hash = '%q'", + "WHERE hash = ?", hash().c_str()); I(res.size() == 0 || res.size() == 1); return (res.size() == 1); @@ -2123,7 +2108,7 @@ fetch(res, 5, one_row, "SELECT id, name, value, keypair, signature " "FROM manifest_certs " - "WHERE hash = '%q'", + "WHERE hash = ?", hash().c_str()); results_to_certs(res, certs); I(certs.size() == 1); @@ -2171,10 +2156,12 @@ results res; completions.clear(); + string pattern = partial + "*"; + fetch(res, 1, any_rows, - "SELECT id FROM revisions WHERE id GLOB '%q*'", - partial.c_str()); - + "SELECT id FROM revisions WHERE id GLOB ?", + pattern.c_str()); + for (size_t i = 0; i < res.size(); ++i) completions.insert(revision_id(res[i][0])); } @@ -2187,9 +2174,11 @@ results res; completions.clear(); + string pattern = partial + "*"; + fetch(res, 1, any_rows, - "SELECT id FROM manifests WHERE id GLOB '%q*'", - partial.c_str()); + "SELECT id FROM manifests WHERE id GLOB ?", + pattern.c_str()); for (size_t i = 0; i < res.size(); ++i) completions.insert(manifest_id(res[i][0])); @@ -2197,8 +2186,8 @@ res.clear(); fetch(res, 1, any_rows, - "SELECT id FROM manifest_deltas WHERE id GLOB '%q*'", - partial.c_str()); + "SELECT id FROM manifest_deltas WHERE id GLOB ?", + pattern.c_str()); for (size_t i = 0; i < res.size(); ++i) completions.insert(manifest_id(res[i][0])); @@ -2211,9 +2200,11 @@ results res; completions.clear(); + string pattern = partial + "*"; + fetch(res, 1, any_rows, - "SELECT id FROM files WHERE id GLOB '%q*'", - partial.c_str()); + "SELECT id FROM files WHERE id GLOB ?", + pattern.c_str()); for (size_t i = 0; i < res.size(); ++i) completions.insert(file_id(res[i][0])); @@ -2221,8 +2212,8 @@ res.clear(); fetch(res, 1, any_rows, - "SELECT id FROM file_deltas WHERE id GLOB '%q*'", - partial.c_str()); + "SELECT id FROM file_deltas WHERE id GLOB ?", + pattern.c_str()); for (size_t i = 0; i < res.size(); ++i) completions.insert(file_id(res[i][0])); @@ -2235,9 +2226,11 @@ results res; completions.clear(); + string pattern = partial + "*"; + fetch(res, 2, any_rows, - "SELECT hash, id FROM public_keys WHERE hash GLOB '%q*'", - partial.c_str()); + "SELECT hash, id FROM public_keys WHERE hash GLOB ?", + pattern.c_str()); for (size_t i = 0; i < res.size(); ++i) completions.insert(make_pair(key_id(res[i][0]), utf8(res[i][1]))); @@ -2245,8 +2238,8 @@ res.clear(); fetch(res, 2, any_rows, - "SELECT hash, id FROM private_keys WHERE hash GLOB '%q*'", - partial.c_str()); + "SELECT hash, id FROM private_keys WHERE hash GLOB ?", + pattern.c_str()); for (size_t i = 0; i < res.size(); ++i) completions.insert(make_pair(key_id(res[i][0]), utf8(res[i][1]))); @@ -2450,7 +2443,7 @@ results res; fetch(res, 2, any_rows, "SELECT branch, epoch FROM branch_epochs" - " WHERE hash = '%q'", + " WHERE hash = ?", eid.inner()().c_str()); I(res.size() == 1); base64 encoded(idx(idx(res, 0), 0)); @@ -2463,7 +2456,7 @@ { results res; fetch(res, one_col, any_rows, - "SELECT hash FROM branch_epochs WHERE hash = '%q'", + "SELECT hash FROM branch_epochs WHERE hash = ?", eid.inner()().c_str()); I(res.size() == 1 || res.size() == 0); return res.size() == 1; @@ -2477,7 +2470,7 @@ encode_base64(branch, encoded); epoch_hash_code(branch, epo, eid); I(epo.inner()().size() == constants::epochlen); - execute("INSERT OR REPLACE INTO branch_epochs VALUES('%q', '%q', '%q')", + execute("INSERT OR REPLACE INTO branch_epochs VALUES(?, ?, ?)", eid.inner()().c_str(), encoded().c_str(), epo.inner()().c_str()); } @@ -2486,7 +2479,7 @@ { base64 encoded; encode_base64(branch, encoded); - execute("DELETE FROM branch_epochs WHERE branch = '%q'", encoded().c_str()); + execute("DELETE FROM branch_epochs WHERE branch = ?", encoded().c_str()); } // vars @@ -2539,7 +2532,7 @@ encode_base64(key.second, name_encoded); base64 value_encoded; encode_base64(value, value_encoded); - execute("INSERT OR REPLACE INTO db_vars VALUES('%q', '%q', '%q')", + execute("INSERT OR REPLACE INTO db_vars VALUES(?, ?, ?)", key.first().c_str(), name_encoded().c_str(), value_encoded().c_str()); @@ -2550,7 +2543,7 @@ { base64 name_encoded; encode_base64(key.second, name_encoded); - execute("DELETE FROM db_vars WHERE domain = '%q' AND name = '%q'", + execute("DELETE FROM db_vars WHERE domain = ? AND name = ?", key.first().c_str(), name_encoded().c_str()); } --- database.hh +++ database.hh @@ -16,6 +16,8 @@ #include +#include + #include "selectors.hh" #include "manifest.hh" #include "numeric_vocab.hh" @@ -72,6 +74,14 @@ std::string const schema; void check_schema(); + struct statement { + int count; + statement() : count(0) {} + sqlite3_stmt *stmt; + }; + + std::map statement_cache; + struct app_state * __app; struct sqlite3 * __sql; struct sqlite3 * sql(bool init = false); @@ -81,12 +91,20 @@ void install_views(); typedef std::vector< std::vector > results; + void execute(char const * query, ...); + void fetch(results & res, int const want_cols, int const want_rows, char const * query, ...); - + + void fetch(results & res, + int const want_cols, + int const want_rows, + char const * query, + va_list args); + bool exists(hexenc const & ident, std::string const & table); bool delta_exists(hexenc const & ident, --- schema_migration.cc +++ schema_migration.cc @@ -38,9 +38,6 @@ typedef boost::tokenizer > tokenizer; extern "C" { -// some wrappers to ease migration - int sqlite3_exec_printf(sqlite3*,const char *sqlFormat,sqlite3_callback, - void *,char **errmsg,...); const char *sqlite3_value_text_s(sqlite3_value *v); } @@ -156,15 +153,15 @@ { id.clear(); string tmp, tmp2; - int res = sqlite3_exec_printf(sql, - "SELECT sql FROM sqlite_master " - "WHERE type = 'table' OR type = 'index' " - // filter out NULL sql statements, because - // those are auto-generated indices (for - // UNIQUE constraints, etc.). - "AND sql IS NOT NULL " - "ORDER BY name", - &append_sql_stmt, &tmp, NULL); + int res = sqlite3_exec(sql, + "SELECT sql FROM sqlite_master " + "WHERE (type = 'table' OR type = 'index') " + // filter out NULL sql statements, because + // those are auto-generated indices (for + // UNIQUE constraints, etc.). + "AND sql IS NOT NULL " + "ORDER BY name", + &append_sql_stmt, &tmp, NULL); if (res != SQLITE_OK) { sqlite3_exec(sql, "ROLLBACK", NULL, NULL, NULL); @@ -271,21 +268,28 @@ char const * dstname, char const * dstschema) { - int res = - sqlite3_exec_printf(sql, "CREATE TABLE %s %s", NULL, NULL, errmsg, - dstname, dstschema); + string create = "CREATE TABLE "; + create += dstname; + create += " "; + create += dstschema; + + int res = sqlite3_exec(sql, create.c_str(), NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = - sqlite3_exec_printf(sql, "INSERT INTO %s SELECT * FROM %s", - NULL, NULL, errmsg, dstname, srcname); + string insert = "INSERT INTO "; + insert += dstname; + insert += " SELECT * FROM "; + insert += srcname; + + res = sqlite3_exec(sql, insert.c_str(), NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - - res = - sqlite3_exec_printf(sql, "DROP TABLE %s", - NULL, NULL, errmsg, srcname); + + string drop = "DROP TABLE "; + drop += srcname; + + res = sqlite3_exec(sql, drop.c_str(), NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -309,23 +313,23 @@ ")")) return false; - int res = sqlite3_exec_printf(sql, "CREATE TABLE posting_queue " - "(" - "url not null, -- URL we are going to send this to\n" - "content not null -- the packets we're going to send\n" - ")", NULL, NULL, errmsg); + int res = sqlite3_exec(sql, "CREATE TABLE posting_queue " + "(" + "url not null, -- URL we are going to send this to\n" + "content not null -- the packets we're going to send\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "INSERT INTO posting_queue " - "SELECT " - "(url || '/' || groupname), " - "content " - "FROM tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "INSERT INTO posting_queue " + "SELECT " + "(url || '/' || groupname), " + "content " + "FROM tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -341,23 +345,23 @@ ")")) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE incoming_queue " - "(" - "url not null, -- URL we got this bundle from\n" - "content not null -- the packets we're going to read\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE incoming_queue " + "(" + "url not null, -- URL we got this bundle from\n" + "content not null -- the packets we're going to read\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "INSERT INTO incoming_queue " - "SELECT " - "(url || '/' || groupname), " - "content " - "FROM tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "INSERT INTO incoming_queue " + "SELECT " + "(url || '/' || groupname), " + "content " + "FROM tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -376,25 +380,25 @@ )) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE sequence_numbers " - "(" - "url primary key, -- URL to read from\n" - "major not null, -- 0 in news servers, may be higher in depots\n" - "minor not null -- last article / packet sequence number we got\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE sequence_numbers " + "(" + "url primary key, -- URL to read from\n" + "major not null, -- 0 in news servers, may be higher in depots\n" + "minor not null -- last article / packet sequence number we got\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "INSERT INTO sequence_numbers " - "SELECT " - "(url || '/' || groupname), " - "major, " - "minor " - "FROM tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "INSERT INTO sequence_numbers " + "SELECT " + "(url || '/' || groupname), " + "major, " + "minor " + "FROM tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -412,24 +416,24 @@ )) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE netserver_manifests " - "(" - "url not null, -- url of some server\n" - "manifest not null, -- manifest which exists on url\n" - "unique(url, manifest)" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE netserver_manifests " + "(" + "url not null, -- url of some server\n" + "manifest not null, -- manifest which exists on url\n" + "unique(url, manifest)" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "INSERT INTO netserver_manifests " - "SELECT " - "(url || '/' || groupname), " - "manifest " - "FROM tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "INSERT INTO netserver_manifests " + "SELECT " + "(url || '/' || groupname), " + "manifest " + "FROM tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -455,28 +459,28 @@ ")")) return false; - int res = sqlite3_exec_printf(sql, "CREATE TABLE manifest_certs\n" - "(\n" - "hash not null unique, -- hash of remaining fields separated by \":\"\n" - "id not null, -- joins with manifests.id or manifest_deltas.id\n" - "name not null, -- opaque string chosen by user\n" - "value not null, -- opaque blob\n" - "keypair not null, -- joins with public_keys.id\n" - "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n" - "unique(name, id, value, keypair, signature)\n" - ")", NULL, NULL, errmsg); + int res = sqlite3_exec(sql, "CREATE TABLE manifest_certs\n" + "(\n" + "hash not null unique, -- hash of remaining fields separated by \":\"\n" + "id not null, -- joins with manifests.id or manifest_deltas.id\n" + "name not null, -- opaque string chosen by user\n" + "value not null, -- opaque blob\n" + "keypair not null, -- joins with public_keys.id\n" + "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n" + "unique(name, id, value, keypair, signature)\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "INSERT INTO manifest_certs " - "SELECT " - "sha1(':', id, name, value, keypair, signature), " - "id, name, value, keypair, signature " - "FROM tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "INSERT INTO manifest_certs " + "SELECT " + "sha1(':', id, name, value, keypair, signature), " + "id, name, value, keypair, signature " + "FROM tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -494,28 +498,28 @@ ")")) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE file_certs\n" - "(\n" - "hash not null unique, -- hash of remaining fields separated by \":\"\n" - "id not null, -- joins with files.id or file_deltas.id\n" - "name not null, -- opaque string chosen by user\n" - "value not null, -- opaque blob\n" - "keypair not null, -- joins with public_keys.id\n" - "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n" - "unique(name, id, value, keypair, signature)\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE file_certs\n" + "(\n" + "hash not null unique, -- hash of remaining fields separated by \":\"\n" + "id not null, -- joins with files.id or file_deltas.id\n" + "name not null, -- opaque string chosen by user\n" + "value not null, -- opaque blob\n" + "keypair not null, -- joins with public_keys.id\n" + "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n" + "unique(name, id, value, keypair, signature)\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "INSERT INTO file_certs " - "SELECT " - "sha1(':', id, name, value, keypair, signature), " - "id, name, value, keypair, signature " - "FROM tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "INSERT INTO file_certs " + "SELECT " + "sha1(':', id, name, value, keypair, signature), " + "id, name, value, keypair, signature " + "FROM tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -529,24 +533,24 @@ ")")) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE public_keys\n" - "(\n" - "hash not null unique, -- hash of remaining fields separated by \":\"\n" - "id primary key, -- key identifier chosen by user\n" - "keydata not null -- RSA public params\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE public_keys\n" + "(\n" + "hash not null unique, -- hash of remaining fields separated by \":\"\n" + "id primary key, -- key identifier chosen by user\n" + "keydata not null -- RSA public params\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "INSERT INTO public_keys " - "SELECT " - "sha1(':', id, keydata), " - "id, keydata " - "FROM tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "INSERT INTO public_keys " + "SELECT " + "sha1(':', id, keydata), " + "id, keydata " + "FROM tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -560,39 +564,39 @@ ")")) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE private_keys\n" - "(\n" - "hash not null unique, -- hash of remaining fields separated by \":\"\n" - "id primary key, -- as in public_keys (same identifiers, in fact)\n" - "keydata not null -- encrypted RSA private params\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE private_keys\n" + "(\n" + "hash not null unique, -- hash of remaining fields separated by \":\"\n" + "id primary key, -- as in public_keys (same identifiers, in fact)\n" + "keydata not null -- encrypted RSA private params\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "INSERT INTO private_keys " - "SELECT " - "sha1(':', id, keydata), " - "id, keydata " - "FROM tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "INSERT INTO private_keys " + "SELECT " + "sha1(':', id, keydata), " + "id, keydata " + "FROM tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; // add the merkle tree stuff - res = sqlite3_exec_printf(sql, - "CREATE TABLE merkle_nodes\n" - "(\n" - "type not null, -- \"key\", \"mcert\", \"fcert\", \"manifest\"\n" - "collection not null, -- name chosen by user\n" - "level not null, -- tree level this prefix encodes\n" - "prefix not null, -- label identifying node in tree\n" - "body not null, -- binary, base64'ed node contents\n" - "unique(type, collection, level, prefix)\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, + "CREATE TABLE merkle_nodes\n" + "(\n" + "type not null, -- \"key\", \"mcert\", \"fcert\", \"manifest\"\n" + "collection not null, -- name chosen by user\n" + "level not null, -- tree level this prefix encodes\n" + "prefix not null, -- label identifying node in tree\n" + "body not null, -- binary, base64'ed node contents\n" + "unique(type, collection, level, prefix)\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -605,74 +609,74 @@ { int res; - res = sqlite3_exec_printf(sql, "DROP TABLE schema_version;", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE schema_version;", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE posting_queue;", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE posting_queue;", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE incoming_queue;", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE incoming_queue;", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE sequence_numbers;", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE sequence_numbers;", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE file_certs;", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE file_certs;", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE netserver_manifests;", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE netserver_manifests;", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "DROP TABLE merkle_nodes;", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE merkle_nodes;", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, - "CREATE TABLE merkle_nodes\n" - "(\n" - "type not null, -- \"key\", \"mcert\", \"fcert\", \"rcert\"\n" - "collection not null, -- name chosen by user\n" - "level not null, -- tree level this prefix encodes\n" - "prefix not null, -- label identifying node in tree\n" - "body not null, -- binary, base64'ed node contents\n" - "unique(type, collection, level, prefix)\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, + "CREATE TABLE merkle_nodes\n" + "(\n" + "type not null, -- \"key\", \"mcert\", \"fcert\", \"rcert\"\n" + "collection not null, -- name chosen by user\n" + "level not null, -- tree level this prefix encodes\n" + "prefix not null, -- label identifying node in tree\n" + "body not null, -- binary, base64'ed node contents\n" + "unique(type, collection, level, prefix)\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE revision_certs\n" - "(\n" - "hash not null unique, -- hash of remaining fields separated by \":\"\n" - "id not null, -- joins with revisions.id\n" - "name not null, -- opaque string chosen by user\n" - "value not null, -- opaque blob\n" - "keypair not null, -- joins with public_keys.id\n" - "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n" - "unique(name, id, value, keypair, signature)\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE revision_certs\n" + "(\n" + "hash not null unique, -- hash of remaining fields separated by \":\"\n" + "id not null, -- joins with revisions.id\n" + "name not null, -- opaque string chosen by user\n" + "value not null, -- opaque blob\n" + "keypair not null, -- joins with public_keys.id\n" + "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n" + "unique(name, id, value, keypair, signature)\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE revisions\n" - "(\n" - "id primary key, -- SHA1(text of revision)\n" - "data not null -- compressed, encoded contents of a revision\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE revisions\n" + "(\n" + "id primary key, -- SHA1(text of revision)\n" + "data not null -- compressed, encoded contents of a revision\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, "CREATE TABLE revision_ancestry\n" - "(\n" - "parent not null, -- joins with revisions.id\n" - "child not null, -- joins with revisions.id\n" - "unique(parent, child)\n" - ")", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "CREATE TABLE revision_ancestry\n" + "(\n" + "parent not null, -- joins with revisions.id\n" + "child not null, -- joins with revisions.id\n" + "unique(parent, child)\n" + ")", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -686,19 +690,18 @@ { int res; - res = sqlite3_exec_printf(sql, "DROP TABLE merkle_nodes;", NULL, NULL, errmsg); + res = sqlite3_exec(sql, "DROP TABLE merkle_nodes;", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, - - "CREATE TABLE branch_epochs\n" - "(\n" - "hash not null unique, -- hash of remaining fields separated by \":\"\n" - "branch not null unique, -- joins with revision_certs.value\n" - "epoch not null -- random hex-encoded id\n" - ");", NULL, NULL, errmsg); + res = sqlite3_exec(sql, + "CREATE TABLE branch_epochs\n" + "(\n" + "hash not null unique, -- hash of remaining fields separated by \":\"\n" + "branch not null unique, -- joins with revision_certs.value\n" + "epoch not null -- random hex-encoded id\n" + ");", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -711,14 +714,14 @@ { int res; - res = sqlite3_exec_printf(sql, - "CREATE TABLE db_vars\n" - "(\n" - "domain not null, -- scope of application of a var\n" - "name not null, -- var key\n" - "value not null, -- var value\n" - "unique(domain, name)\n" - ");", NULL, NULL, errmsg); + res = sqlite3_exec(sql, + "CREATE TABLE db_vars\n" + "(\n" + "domain not null, -- scope of application of a var\n" + "name not null, -- var key\n" + "value not null, -- var value\n" + "unique(domain, name)\n" + ");", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -731,24 +734,24 @@ { int res; - res = sqlite3_exec_printf(sql, - "CREATE INDEX revision_ancestry__child " - "ON revision_ancestry (child)", - NULL, NULL, errmsg); + res = sqlite3_exec(sql, + "CREATE INDEX revision_ancestry__child " + "ON revision_ancestry (child)", + NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, - "CREATE INDEX revision_certs__id " - "ON revision_certs (id);", - NULL, NULL, errmsg); + res = sqlite3_exec(sql, + "CREATE INDEX revision_certs__id " + "ON revision_certs (id);", + NULL, NULL, errmsg); if (res != SQLITE_OK) return false; - res = sqlite3_exec_printf(sql, - "CREATE INDEX revision_certs__name_value " - "ON revision_certs (name, value);", - NULL, NULL, errmsg); + res = sqlite3_exec(sql, + "CREATE INDEX revision_certs__name_value " + "ON revision_certs (name, value);", + NULL, NULL, errmsg); if (res != SQLITE_OK) return false; --- tests/t_merge_binary.at +++ tests/t_merge_binary.at @@ -0,0 +1,240 @@ +AT_SETUP([merge binary file]) +MONOTONE_SETUP + +NEED_UNB64 + +# This is a real merge error. A binary file is happily merged by monotone +# just because contains some strategically placed line feeds +AT_XFAIL_IF(true) + +AT_DATA(parent.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsyGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxsKCgoK +]) + +AT_DATA(left.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC +QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsypzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3 +pzq3pzq3pzq3GxsKCgoK +]) + +AT_DATA(right.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtQApC +OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtQApC2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy +2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsyGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb +GxvbGxvbGxvbGxsKCgoK +]) + +UNB64(parent.bmp.b64, parent.bmp) +UNB64(left.bmp.b64, left.bmp) +UNB64(right.bmp.b64, right.bmp) + +AT_CHECK(cp -f parent.bmp testfile.bmp) +AT_CHECK(MONOTONE add testfile.bmp, [], [ignore], [ignore]) +COMMIT(testbranch) +PARENT_SHA=`BASE_REVISION` + +AT_CHECK(cp -f left.bmp testfile.bmp) +COMMIT(testbranch) + +REVERT_TO($PARENT_SHA) + +AT_CHECK(cp -f right.bmp testfile.bmp) +COMMIT(testbranch) + +# merge should fail! +AT_CHECK(MONOTONE --branch=testbranch merge, [1], [ignore], [ignore]) + +AT_CLEANUP --- tests/t_update_off_branch.at +++ tests/t_update_off_branch.at