# # # patch "README.encapsulation" # from [d3004d624009fb32985b6fd253eb58abe6836471] # to [ab34e03dfbc148e390fdfb223238f3d9666ad65a] # # patch "asciik.cc" # from [efcaa25942bd9117b5a68a4a2850095aa597dc1d] # to [ea0239139f8e1f52038e4c4d1930ac1e10807bad] # # patch "automate.cc" # from [d09f4d5c7b28028f706714c98efebff98fecc3a9] # to [6332b775a3ffcf6161e5e991539fb1d02675299e] # # patch "cmd.hh" # from [12b3632e47447c7749697a8bbb36a7d0b08af268] # to [a77c1d7d4c48bc9f0494f2814adfe04c0925b4dc] # # patch "cmd_db.cc" # from [8bc592fb9d5ee0a4810e6cc4161be06c863f2fe4] # to [b67e68899c1806c94e0b04b7a81ba70b3b3d1f50] # # patch "cmd_diff_log.cc" # from [eae3ce2b4f7e22b659b211efb893aec61d0b2647] # to [34f7387970c52d236c10a624d55f951dbaea5a16] # # patch "cmd_files.cc" # from [d598d5629971c8bb3d857f38de94320dec8c2ac7] # to [ef7694dc67de6546d6de36d3836de1ec1486e6fa] # # patch "cmd_key_cert.cc" # from [346ceb2fb46108e32db112dc6310ad812aa9a7d4] # to [35113296a43d1362967b899619a2d7f95f67dc98] # # patch "cmd_list.cc" # from [d6affac89cf331ad5b46a6dbe402acf23c693808] # to [ed559ca16f4350b0e8a3fde62db6ca5dec2d67f5] # # patch "cmd_merging.cc" # from [0fa3eb83d02680c4be7da772663b8c7b8ecfb481] # to [260a66f036397e43af27f23c28320fb2a27465ab] # # patch "cmd_netsync.cc" # from [17573664fcdf96f623a7990da0de0e9cdbfaed43] # to [83237c7973165292c7a530899e2f8d3e8970593b] # # patch "cmd_ws_commit.cc" # from [65bb925c7d7345dc7f7769f67a0f2f01b3b2bca8] # to [ebdd9ca267a2110271afec9c76330a1fe51d6e90] # # patch "commands.cc" # from [f96ab9a2476a98c91700778177d34d5050446548] # to [74d824a3731a743a29a505e898a8ecc1735662e4] # # patch "database.cc" # from [067dba3d50831eb3559c75ab4405ae20098220dd] # to [3d56cf585bb8ed3bb3589e5b8fc5f548a8d374dd] # # patch "database.hh" # from [961df1f9744a0c06c4a7729275e4cc5b6fb8cdb5] # to [cb2f020ae339ce2fccb1826b8b8b6553571b4354] # # patch "selectors.cc" # from [80b66b9b6cd6c8a7108616f3497a9a422c42855c] # to [ce99f29673f959d809842a0233bd116422f06e2f] # # patch "selectors.hh" # from [8403b2915609ef93a8287a336ba4b59262563c1a] # to [d717061d1740519d27e3381fd91e46ce032dfbed] # ============================================================ --- README.encapsulation d3004d624009fb32985b6fd253eb58abe6836471 +++ README.encapsulation ab34e03dfbc148e390fdfb223238f3d9666ad65a @@ -1,21 +1,12 @@ database.cc: database.cc: __app is a member variable of class database, used by: - normal member functions: - database::complete() - __app->require_workspace() - __app->opts.branchname - __app->get_project() - accessor hacks: __app->keys get_key_store() __app->lua ... - hook_exists() - hook_expand_selector() - hook_expand_date() hook_get_manifest_cert_trust() hook_get_revision_cert_trust() hook_get_author() ============================================================ --- asciik.cc efcaa25942bd9117b5a68a4a2850095aa597dc1d +++ asciik.cc ea0239139f8e1f52038e4c4d1930ac1e10807bad @@ -374,27 +374,15 @@ CMD(asciik, "asciik", "", CMD_REF(debug) N(args.size() == 1, F("wrong argument count")); - vector > - sels(selectors::parse_selector(args[0](), app.db)); + set revs; + complete(app, idx(args, 0)(), revs); - // we jam through an "empty" selection on sel_ident type - set completions; - //set> completions; - selectors::selector_type ty = selectors::sel_ident; - selectors::complete_selector("", sels, ty, completions, app.db); - - asciik graph(std::cout, 10); - set revs; - for (set::const_iterator i = completions.begin(); - i != completions.end(); ++i) - { - revision_id rid(*i); - revs.insert(rid); - } vector sorted; toposort(revs, sorted, app.db); - vector curr_row; reverse(sorted.begin(), sorted.end()); + + asciik graph(std::cout, 10); + for (vector::const_iterator rev = sorted.begin(); rev != sorted.end(); ++rev) { ============================================================ --- automate.cc d09f4d5c7b28028f706714c98efebff98fecc3a9 +++ automate.cc 6332b775a3ffcf6161e5e991539fb1d02675299e @@ -500,16 +500,10 @@ CMD_AUTOMATE(select, N_("SELECTOR"), F("wrong argument count")); CMD_REQUIRES_DATABASE(app); + set completions; + expand_selector(app, idx(args, 0)(), completions); - vector > - sels(selectors::parse_selector(args[0](), db)); - - // we jam through an "empty" selection on sel_ident type - set completions; - selectors::selector_type ty = selectors::sel_ident; - selectors::complete_selector("", sels, ty, completions, db); - - for (set::const_iterator i = completions.begin(); + for (set::const_iterator i = completions.begin(); i != completions.end(); ++i) output << *i << '\n'; } ============================================================ --- cmd.hh 12b3632e47447c7749697a8bbb36a7d0b08af268 +++ cmd.hh a77c1d7d4c48bc9f0494f2814adfe04c0925b4dc @@ -14,6 +14,7 @@ #include #include "commands.hh" +#include "selectors.hh" #include "options.hh" #include "sanity.hh" @@ -159,18 +160,6 @@ void revision_id const & id); void -complete(database & db, project_t & project, - std::string const & str, - revision_id & completion, - bool must_exist=true); - -void -complete(database & db, - std::string const & str, - std::set & completion, - bool must_exist=true); - -void notify_if_multiple_heads(project_t & project, branch_name const & branchname); void ============================================================ --- cmd_db.cc 8bc592fb9d5ee0a4810e6cc4161be06c863f2fe4 +++ cmd_db.cc b67e68899c1806c94e0b04b7a81ba70b3b3d1f50 @@ -122,7 +122,7 @@ CMD(db_kill_rev_locally, "kill_rev_local revision_id revid; - complete(app.db, app.get_project(), idx(args, 0)(), revid); + complete(app, idx(args, 0)(), revid); N(app.db.revision_exists(revid), F("no such revision '%s'") % revid); ============================================================ --- cmd_diff_log.cc eae3ce2b4f7e22b659b211efb893aec61d0b2647 +++ cmd_diff_log.cc 34f7387970c52d236c10a624d55f951dbaea5a16 @@ -396,8 +396,7 @@ prepare_diff(cset & included, roster_t old_roster, restricted_roster, new_roster; revision_id r_old_id; - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), r_old_id); + complete(app, idx(app.opts.revision_selectors, 0)(), r_old_id); N(app.db.revision_exists(r_old_id), F("no such revision '%s'") % r_old_id); @@ -425,10 +424,8 @@ prepare_diff(cset & included, roster_t old_roster, restricted_roster, new_roster; revision_id r_old_id, r_new_id; - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), r_old_id); - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 1)(), r_new_id); + complete(app, idx(app.opts.revision_selectors, 0)(), r_old_id); + complete(app, idx(app.opts.revision_selectors, 1)(), r_new_id); N(app.db.revision_exists(r_old_id), F("no such revision '%s'") % r_old_id); @@ -663,7 +660,7 @@ CMD(log, "log", "", CMD_REF(informative) i != app.opts.from.end(); i++) { set rids; - complete(app.db, (*i)(), rids); + complete(app, (*i)(), rids); for (set::const_iterator j = rids.begin(); j != rids.end(); ++j) { @@ -719,7 +716,7 @@ CMD(log, "log", "", CMD_REF(informative) { MM(*i); set rids; - complete(app.db, (*i)(), rids); + complete(app, (*i)(), rids); for (set::const_iterator j = rids.begin(); j != rids.end(); ++j) { ============================================================ --- cmd_files.cc d598d5629971c8bb3d857f38de94320dec8c2ac7 +++ cmd_files.cc ef7694dc67de6546d6de36d3836de1ec1486e6fa @@ -168,8 +168,7 @@ CMD(annotate, "annotate", "", CMD_REF(in } else { - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), rid); + complete(app, idx(app.opts.revision_selectors, 0)(), rid); N(!null_id(rid), F("no revision for file '%s' in database") % file); N(app.db.revision_exists(rid), @@ -305,8 +304,7 @@ CMD(cat, "cat", "", CMD_REF(informative) rid = parent_id(parents.begin()); } else - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), rid); + complete(app, idx(app.opts.revision_selectors, 0)(), rid); dump_file(cout, app.db, rid, idx(args, 0)); } @@ -369,10 +367,8 @@ CMD_AUTOMATE(get_file_of, N_("FILENAME") rid = parent_id(parents.begin()); } else - complete(db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), rid); + complete(app, idx(app.opts.revision_selectors, 0)(), rid); - // FIXME: again, dump_file should not take app arg dump_file(output, db, rid, idx(args, 0)); } ============================================================ --- cmd_key_cert.cc 346ceb2fb46108e32db112dc6310ad812aa9a7d4 +++ cmd_key_cert.cc 35113296a43d1362967b899619a2d7f95f67dc98 @@ -193,7 +193,7 @@ CMD(cert, "cert", "", CMD_REF(key_and_ce transaction_guard guard(app.db); revision_id rid; - complete(app.db, app.get_project(), idx(args, 0)(), rid); + complete(app, idx(args, 0)(), rid); cert_name cname; internalize_cert_name(idx(args, 1), cname); @@ -224,10 +224,14 @@ CMD(trusted, "trusted", "", CMD_REF(key_ if (args.size() < 4) throw usage(execid); - revision_id rid; - complete(app.db, app.get_project(), idx(args, 0)(), rid, false); - hexenc ident(rid.inner()); + set rids; + expand_selector(app, idx(args, 0)(), rids); + diagnose_ambiguous_expansion(app, idx(args, 0)(), rids); + hexenc ident; + if (!rids.empty()) + ident = rids.begin()->inner(); + cert_name cname; internalize_cert_name(idx(args, 1), cname); @@ -272,7 +276,7 @@ CMD(tag, "tag", "", CMD_REF(review), N_( throw usage(execid); revision_id r; - complete(app.db, app.get_project(), idx(args, 0)(), r); + complete(app, idx(args, 0)(), r); cert_revision_tag(r, idx(args, 1)(), app.db); } @@ -287,7 +291,7 @@ CMD(testresult, "testresult", "", CMD_RE throw usage(execid); revision_id r; - complete(app.db, app.get_project(), idx(args, 0)(), r); + complete(app, idx(args, 0)(), r); cert_revision_testresult(r, idx(args, 1)(), app.db); } @@ -301,7 +305,7 @@ CMD(approve, "approve", "", CMD_REF(revi throw usage(execid); revision_id r; - complete(app.db, app.get_project(), idx(args, 0)(), r); + complete(app, idx(args, 0)(), r); guess_branch(r, app.db, app.get_project()); N(app.opts.branchname() != "", F("need --branch argument for approval")); app.get_project().put_revision_in_branch(r, app.opts.branchname); @@ -316,7 +320,7 @@ CMD(suspend, "suspend", "", CMD_REF(revi throw usage(execid); revision_id r; - complete(app.db, app.get_project(), idx(args, 0)(), r); + complete(app, idx(args, 0)(), r); guess_branch(r, app.db, app.get_project()); N(app.opts.branchname() != "", F("need --branch argument to suspend")); app.get_project().suspend_revision_in_branch(r, app.opts.branchname); @@ -345,7 +349,7 @@ CMD(comment, "comment", "", CMD_REF(revi F("empty comment")); revision_id r; - complete(app.db, app.get_project(), idx(args, 0)(), r); + complete(app, idx(args, 0)(), r); cert_revision_comment(r, comment, app.db); } ============================================================ --- cmd_list.cc d6affac89cf331ad5b46a6dbe402acf23c693808 +++ cmd_list.cc ed559ca16f4350b0e8a3fde62db6ca5dec2d67f5 @@ -62,7 +62,7 @@ CMD(certs, "certs", "", CMD_REF(list), " transaction_guard guard(app.db, false); revision_id ident; - complete(app.db, app.get_project(), idx(args, 0)(), ident); + complete(app, idx(args, 0)(), ident); vector< revision > ts; // FIXME_PROJECTS: after projects are implemented, // use the app.db version instead if no project is specified. ============================================================ --- cmd_merging.cc 0fa3eb83d02680c4be7da772663b8c7b8ecfb481 +++ cmd_merging.cc 260a66f036397e43af27f23c28320fb2a27465ab @@ -190,8 +190,7 @@ CMD(update, "update", "", CMD_REF(worksp } else { - complete(app.db, app.get_project(), - app.opts.revision_selectors[0](), chosen_rid); + complete(app, app.opts.revision_selectors[0](), chosen_rid); N(app.db.revision_exists(chosen_rid), F("no such revision '%s'") % chosen_rid); } @@ -695,7 +694,7 @@ CMD(merge_into_workspace, "merge_into_wo calculate_ident(working_rev, working_rid); } - complete(app.db, app.get_project(), idx(args, 0)(), right_id); + complete(app, idx(args, 0)(), right_id); app.db.get_roster(right_id, right); N(!(left_id == right_id), F("workspace is already at revision %s") % left_id); @@ -767,8 +766,8 @@ CMD(explicit_merge, "explicit_merge", "" if (args.size() != 3) throw usage(execid); - complete(app.db, app.get_project(), idx(args, 0)(), left); - complete(app.db, app.get_project(), idx(args, 1)(), right); + complete(app, idx(args, 0)(), left); + complete(app, idx(args, 1)(), right); branch = branch_name(idx(args, 2)()); N(!(left == right), @@ -790,8 +789,8 @@ CMD(show_conflicts, "show_conflicts", "" if (args.size() != 2) throw usage(execid); revision_id l_id, r_id; - complete(app.db, app.get_project(), idx(args,0)(), l_id); - complete(app.db, app.get_project(), idx(args,1)(), r_id); + complete(app, idx(args,0)(), l_id); + complete(app, idx(args,1)(), r_id); N(!is_ancestor(l_id, r_id, app.db), F("%s is an ancestor of %s; no merge is needed.") % l_id % r_id); N(!is_ancestor(r_id, l_id, app.db), @@ -857,8 +856,7 @@ CMD(pluck, "pluck", "", CMD_REF(workspac if (app.opts.revision_selectors.size() == 1) { - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), to_rid); + complete(app, idx(app.opts.revision_selectors, 0)(), to_rid); N(app.db.revision_exists(to_rid), F("no such revision '%s'") % to_rid); std::set parents; @@ -873,12 +871,10 @@ CMD(pluck, "pluck", "", CMD_REF(workspac } else if (app.opts.revision_selectors.size() == 2) { - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), from_rid); + complete(app, idx(app.opts.revision_selectors, 0)(), from_rid); N(app.db.revision_exists(from_rid), F("no such revision '%s'") % from_rid); - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 1)(), to_rid); + complete(app, idx(app.opts.revision_selectors, 1)(), to_rid); N(app.db.revision_exists(to_rid), F("no such revision '%s'") % to_rid); } @@ -1115,7 +1111,7 @@ CMD(get_roster, "get_roster", "", CMD_RE else if (args.size() == 1) { revision_id rid; - complete(app.db, app.get_project(), idx(args, 0)(), rid); + complete(app, idx(args, 0)(), rid); I(!null_id(rid)); app.db.get_roster(rid, roster, mm); } ============================================================ --- cmd_netsync.cc 17573664fcdf96f623a7990da0de0e9cdbfaed43 +++ cmd_netsync.cc 83237c7973165292c7a530899e2f8d3e8970593b @@ -359,8 +359,7 @@ CMD(clone, "clone", "", CMD_REF(network) else if (app.opts.revision_selectors.size() == 1) { // use specified revision - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), ident); + complete(app, idx(app.opts.revision_selectors, 0)(), ident); N(app.db.revision_exists(ident), F("no such revision '%s'") % ident); ============================================================ --- cmd_ws_commit.cc 65bb925c7d7345dc7f7769f67a0f2f01b3b2bca8 +++ cmd_ws_commit.cc ebdd9ca267a2110271afec9c76330a1fe51d6e90 @@ -296,7 +296,7 @@ CMD(disapprove, "disapprove", "", CMD_RE revision_id r; revision_t rev, rev_inverse; shared_ptr cs_inverse(new cset()); - complete(app.db, app.get_project(), idx(args, 0)(), r); + complete(app, idx(args, 0)(), r); app.db.get_revision(r, rev); N(rev.edges.size() == 1, @@ -561,7 +561,7 @@ CMD(checkout, "checkout", "co", CMD_REF( else if (app.opts.revision_selectors.size() == 1) { // use specified revision - complete(app.db, app.get_project(), + complete(app, idx(app.opts.revision_selectors, 0)(), revid); N(app.db.revision_exists(revid), F("no such revision '%s'") % revid); @@ -1281,8 +1281,7 @@ CMD_NO_WORKSPACE(import, "import", "", C if (app.opts.revision_selectors.size() == 1) { // use specified revision - complete(app.db, app.get_project(), - idx(app.opts.revision_selectors, 0)(), ident); + complete(app, idx(app.opts.revision_selectors, 0)(), ident); N(app.db.revision_exists(ident), F("no such revision '%s'") % ident); ============================================================ --- commands.cc f96ab9a2476a98c91700778177d34d5050446548 +++ commands.cc 74d824a3731a743a29a505e898a8ecc1735662e4 @@ -887,74 +887,7 @@ describe_revision(database & db, project return description; } - void -complete(database & db, - string const & str, - set & completion, - bool must_exist) -{ - // This copies the start of selectors::parse_selector().to avoid - // getting a log when there's no expansion happening...: - // - // this rule should always be enabled, even if the user specifies - // --norc: if you provide a revision id, you get a revision id. - if (str.find_first_not_of(constants::legal_id_bytes) == string::npos - && str.size() == constants::idlen) - { - completion.insert(revision_id(hexenc(id(str)))); - if (must_exist) - N(db.revision_exists(*completion.begin()), - F("no such revision '%s'") % *completion.begin()); - return; - } - - vector > - sels(selectors::parse_selector(str, db)); - - P(F("expanding selection '%s'") % str); - - // we jam through an "empty" selection on sel_ident type - set completions; - selectors::selector_type ty = selectors::sel_ident; - selectors::complete_selector("", sels, ty, completions, db); - - N(completions.size() != 0, - F("no match for selection '%s'") % str); - - for (set::const_iterator i = completions.begin(); - i != completions.end(); ++i) - { - pair::const_iterator, bool> p = - completion.insert(revision_id(hexenc(id(*i)))); - P(F("expanded to '%s'") % *(p.first)); - } -} - - -void -complete(database & db, project_t & project, - string const & str, - revision_id & completion, - bool must_exist) -{ - set completions; - - complete(db, str, completions, must_exist); - - if (completions.size() > 1) - { - string err = (F("selection '%s' has multiple ambiguous expansions:") % str).str(); - for (set::const_iterator i = completions.begin(); - i != completions.end(); ++i) - err += ("\n" + describe_revision(db, project, *i)); - N(completions.size() == 1, i18n_format(err)); - } - - completion = *completions.begin(); -} - -void notify_if_multiple_heads(project_t & project, branch_name const & branchname) { ============================================================ --- database.cc 067dba3d50831eb3559c75ab4405ae20098220dd +++ database.cc 3d56cf585bb8ed3bb3589e5b8fc5f548a8d374dd @@ -2856,245 +2856,95 @@ database::complete(string const & partia completions.insert(make_pair(key_id(res[i][0]), utf8(res[i][1]))); } -using selectors::selector_type; +// revision selectors -static void selector_to_certname(selector_type ty, - cert_name & name, - string & prefix, - string & suffix) +void +database::select_parent(string const & partial, + set & completions) { - prefix = suffix = "*"; - switch (ty) - { - case selectors::sel_author: - prefix = suffix = ""; - name = author_cert_name; - break; - case selectors::sel_branch: - prefix = suffix = ""; - name = branch_cert_name; - break; - case selectors::sel_head: - prefix = suffix = ""; - name = branch_cert_name; - break; - case selectors::sel_date: - case selectors::sel_later: - case selectors::sel_earlier: - name = date_cert_name; - break; - case selectors::sel_tag: - prefix = suffix = ""; - name = tag_cert_name; - break; - case selectors::sel_ident: - case selectors::sel_cert: - case selectors::sel_unknown: - case selectors::sel_parent: - I(false); // don't do this. - break; - } + results res; + completions.clear(); + + string pattern = partial + "*"; + + fetch(res, 1, any_rows, + query("SELECT DISTINCT parent FROM revision_ancestry WHERE child GLOB ?") + % text(pattern)); + + for (size_t i = 0; i < res.size(); ++i) + completions.insert(revision_id(res[i][0])); } -void database::complete(selector_type ty, - string const & partial, - vector > const & limit, - set & completions) +void +database::select_cert(string const & certname, + set & completions) { - //L(FL("database::complete for partial '%s'") % partial); + results res; completions.clear(); - // step 1: the limit is transformed into an SQL select statement which - // selects a set of IDs from the revision_certs table which match the - // limit. this is done by building an SQL select statement for each term - // in the limit and then INTERSECTing them all. + fetch(res, 1, any_rows, + query("SELECT DISTINCT id FROM revision_certs WHERE name = ?") + % text(certname)); - query lim; - lim.sql_cmd = "("; - if (limit.empty()) - { - lim.sql_cmd += "SELECT id FROM revision_certs"; - } - else - { - bool first_limit = true; - for (vector >::const_iterator i = limit.begin(); - i != limit.end(); ++i) - { - if (first_limit) - first_limit = false; - else - lim.sql_cmd += " INTERSECT "; + for (size_t i = 0; i < res.size(); ++i) + completions.insert(revision_id(res[i][0])); +} - if (i->first == selectors::sel_ident) - { - lim.sql_cmd += "SELECT id FROM revision_certs WHERE id GLOB ?"; - lim % text(i->second + "*"); - } - else if (i->first == selectors::sel_parent) - { - lim.sql_cmd += "SELECT parent AS id FROM revision_ancestry WHERE child GLOB ?"; - lim % text(i->second + "*"); - } - else if (i->first == selectors::sel_cert) - { - if (i->second.length() > 0) - { - size_t spot = i->second.find("="); +void +database::select_cert(string const & certname, string const & certvalue, + set & completions) +{ + results res; + completions.clear(); - if (spot != (size_t)-1) - { - string certname; - string certvalue; + fetch(res, 1, any_rows, + query("SELECT DISTINCT id FROM revision_certs" + " WHERE name = ? AND CAST(value AS TEXT) GLOB ?") + % text(certname) % text(certvalue)); - certname = i->second.substr(0, spot); - spot++; - certvalue = i->second.substr(spot); - lim.sql_cmd += "SELECT id FROM revision_certs WHERE name=? AND CAST(value AS TEXT) glob ?"; - lim % text(certname) % text(certvalue); - } - else - { - lim.sql_cmd += "SELECT id FROM revision_certs WHERE name=?"; - lim % text(i->second); - } + for (size_t i = 0; i < res.size(); ++i) + completions.insert(revision_id(res[i][0])); +} - } - } - else if (i->first == selectors::sel_unknown) - { - lim.sql_cmd += "SELECT id FROM revision_certs WHERE (name=? OR name=? OR name=?)"; - lim % text(author_cert_name()) % text(tag_cert_name()) % text(branch_cert_name()); - lim.sql_cmd += " AND CAST(value AS TEXT) glob ?"; - lim % text(i->second + "*"); - } - else if (i->first == selectors::sel_head) - { - // get branch names - set branch_names; - if (i->second.size() == 0) - { - __app->require_workspace("the empty head selector h: refers to the head of the current branch"); - branch_names.insert(__app->opts.branchname); - } - else - { - __app->get_project().get_branch_list(globish(i->second), branch_names, true); - } +void +database::select_author_tag_or_branch(string const & partial, + set & completions) +{ + results res; + completions.clear(); - L(FL("found %d matching branches") % branch_names.size()); + string pattern = partial + "*"; - // for each branch name, get the branch heads - set heads; - for (set::const_iterator bn = branch_names.begin(); - bn != branch_names.end(); bn++) - { - set branch_heads; - __app->get_project().get_branch_heads(*bn, branch_heads); - heads.insert(branch_heads.begin(), branch_heads.end()); - L(FL("after get_branch_heads for %s, heads has %d entries") % (*bn) % heads.size()); - } + fetch(res, 1, any_rows, + query("SELECT DISTINCT id FROM revision_certs" + " WHERE (name=? OR name=? OR name=?)" + " AND CAST(value AS TEXT) GLOB ?") + % text(author_cert_name()) % text(tag_cert_name()) + % text(branch_cert_name()) % text(pattern)); - lim.sql_cmd += "SELECT id FROM revision_certs WHERE id IN ("; - if (heads.size()) - { - set::const_iterator r = heads.begin(); - lim.sql_cmd += "?"; - lim % text(r->inner()()); - r++; - while (r != heads.end()) - { - lim.sql_cmd += ", ?"; - lim % text(r->inner()()); - r++; - } - } - lim.sql_cmd += ") "; - } - else - { - cert_name certname; - string prefix; - string suffix; - selector_to_certname(i->first, certname, prefix, suffix); - L(FL("processing selector type %d with i->second '%s'") % ty % i->second); - if ((i->first == selectors::sel_branch) && (i->second.size() == 0)) - { - __app->require_workspace("the empty branch selector b: refers to the current branch"); - lim.sql_cmd += "SELECT id FROM revision_certs WHERE name=? AND CAST(value AS TEXT) glob ?"; - lim % text(branch_cert_name()) % text(__app->opts.branchname()); - L(FL("limiting to current branch '%s'") % __app->opts.branchname); - } - else - { - lim.sql_cmd += "SELECT id FROM revision_certs WHERE name=? AND "; - lim % text(certname()); - switch (i->first) - { - case selectors::sel_earlier: - lim.sql_cmd += "value <= ?"; - lim % blob(i->second); - break; - case selectors::sel_later: - lim.sql_cmd += "value > ?"; - lim % blob(i->second); - break; - default: - lim.sql_cmd += "CAST(value AS TEXT) glob ?"; - lim % text(prefix + i->second + suffix); - break; - } - } - } - //L(FL("found selector type %d, selecting_head is now %d") % i->first % selecting_head); - } - } - lim.sql_cmd += ")"; + for (size_t i = 0; i < res.size(); ++i) + completions.insert(revision_id(res[i][0])); +} - // step 2: depending on what we've been asked to disambiguate, we - // will complete either some idents, or cert values, or "unknown" - // which generally means "author, tag or branch" +void +database::select_date(string const & date, string const & comparison, + set & completions) +{ + results res; + completions.clear(); - if (ty == selectors::sel_ident || ty == selectors::sel_parent) - { - lim.sql_cmd = "SELECT id FROM " + lim.sql_cmd; - } - else - { - string prefix = "*"; - string suffix = "*"; - lim.sql_cmd = "SELECT value FROM revision_certs WHERE"; - if (ty == selectors::sel_unknown) - { - lim.sql_cmd += " (name=? OR name=? OR name=?)"; - lim % text(author_cert_name()) % text(tag_cert_name()) % text(branch_cert_name()); - } - else - { - cert_name certname; - selector_to_certname(ty, certname, prefix, suffix); - lim.sql_cmd += " (name=?)"; - lim % text(certname()); - } + query q; + q.sql_cmd = ("SELECT DISTINCT id FROM revision_certs " + "WHERE name = ? AND CAST(value AS TEXT) "); + q.sql_cmd += comparison; + q.sql_cmd += " ?"; - lim.sql_cmd += " AND (CAST(value AS TEXT) GLOB ?) AND (id IN " + lim.sql_cmd + ")"; - lim % text(prefix + partial + suffix); - } - - results res; - fetch(res, one_col, any_rows, lim); + fetch(res, 1, any_rows, + q % text(date_cert_name()) % text(date)); for (size_t i = 0; i < res.size(); ++i) - { - if (ty == selectors::sel_ident) - completions.insert(res[i][0]); - else - { - data row_decoded(res[i][0]); - completions.insert(row_decoded()); - } - } + completions.insert(revision_id(res[i][0])); } - + // epochs void @@ -3515,24 +3365,6 @@ bool // FIXME: the quick hack lua link in functions bool -database::hook_exists(std::string const & name) -{ - return __app->lua.hook_exists(name); -} - -bool -database::hook_expand_selector(std::string const & sel, std::string & exp) -{ - return __app->lua.hook_expand_selector(sel, exp); -}; - -bool -database::hook_expand_date(std::string const & sel, std::string & exp) -{ - return __app->lua.hook_expand_date(sel, exp); -}; - -bool database::hook_get_manifest_cert_trust(set const & signers, hexenc const & id, cert_name const & name, cert_value const & val) { ============================================================ --- database.hh 961df1f9744a0c06c4a7729275e4cc5b6fb8cdb5 +++ database.hh cb2f020ae339ce2fccb1826b8b8b6553571b4354 @@ -24,7 +24,6 @@ int sqlite3_finalize(sqlite3_stmt *); #include "paths.hh" #include "cleanup.hh" #include "roster.hh" -#include "selectors.hh" #include "graph.hh" // FIXME: would be better not to include this everywhere @@ -533,11 +532,20 @@ public: void complete(std::string const & partial, std::set< std::pair > & completions); - void complete(selectors::selector_type ty, - std::string const & partial, - std::vector > const & limit, - std::set & completions); + // + // --== Revision selectors ==-- + // +public: + void select_parent(std::string const & partial, + std::set & completions); + void select_cert(std::string const & certname, + std::set & completions); + void select_cert(std::string const & certname, std::string const & certvalue, + std::set & completions); + void select_author_tag_or_branch(std::string const & partial, + std::set & completions); + void select_date(std::string const & date, std::string const & comparison, + std::set & completions); // // --== The 'db' family of top-level commands ==-- @@ -601,9 +609,6 @@ public: revision_t const & rev); // FIXME: quick hack to make these hooks available via the database context - bool hook_exists(std::string const & name); - bool hook_expand_selector(std::string const & sel, std::string & exp); - bool hook_expand_date(std::string const & sel, std::string & exp); bool hook_get_manifest_cert_trust(std::set const & signers, hexenc const & id, cert_name const & name, cert_value const & val); bool hook_get_revision_cert_trust(std::set const & signers, ============================================================ --- selectors.cc 80b66b9b6cd6c8a7108616f3497a9a422c42855c +++ selectors.cc ce99f29673f959d809842a0233bd116422f06e2f @@ -12,7 +12,11 @@ #include "sanity.hh" #include "constants.hh" #include "database.hh" +#include "app_state.hh" +#include "globish.hh" +#include "cmd.hh" +#include #include using std::make_pair; @@ -20,159 +24,398 @@ using std::vector; using std::set; using std::string; using std::vector; +using std::set_intersection; +using std::inserter; -namespace selectors +enum selector_type + { + sel_author, + sel_branch, + sel_head, + sel_date, + sel_tag, + sel_ident, + sel_cert, + sel_earlier, + sel_later, + sel_parent, + sel_unknown + }; + +typedef vector > selector_list; + +static void +decode_selector(string const & orig_sel, + selector_type & type, + string & sel, + app_state & app) { + sel = orig_sel; - static void - decode_selector(string const & orig_sel, - selector_type & type, - string & sel, - database & db) - { - sel = orig_sel; + L(FL("decoding selector '%s'") % sel); - L(FL("decoding selector '%s'") % sel); + string tmp; + if (sel.size() < 2 || sel[1] != ':') + { + if (!app.lua.hook_expand_selector(sel, tmp)) + { + L(FL("expansion of selector '%s' failed") % sel); + } + else + { + P(F("expanded selector '%s' -> '%s'") % sel % tmp); + sel = tmp; + } + } - string tmp; - if (sel.size() < 2 || sel[1] != ':') + if (sel.size() >= 2 && sel[1] == ':') + { + switch (sel[0]) + { + case 'a': + type = sel_author; + break; + case 'b': + type = sel_branch; + break; + case 'h': + type = sel_head; + break; + case 'd': + type = sel_date; + break; + case 'i': + type = sel_ident; + break; + case 't': + type = sel_tag; + break; + case 'c': + type = sel_cert; + break; + case 'l': + type = sel_later; + break; + case 'e': + type = sel_earlier; + break; + case 'p': + type = sel_parent; + break; + default: + W(F("unknown selector type: %c") % sel[0]); + break; + } + sel.erase(0,2); + + // validate certain selector values and provide defaults + switch (type) + { + case sel_date: + case sel_later: + case sel_earlier: + if (app.lua.hook_exists("expand_date")) + { + N(app.lua.hook_expand_date(sel, tmp), + F("selector '%s' is not a valid date\n") % sel); + } + else + { + // if expand_date is not available, start with something + tmp = sel; + } + + // if we still have a too short datetime string, expand it with + // default values, but only if the type is earlier or later; + // for searching a specific date cert this makes no sense + // FIXME: this is highly speculative if expand_date wasn't called + // beforehand - tmp could be _anything_ but a partial date string + if (tmp.size()<8 && (sel_later==type || sel_earlier==type)) + tmp += "-01T00:00:00"; + else if (tmp.size()<11 && (sel_later==type || sel_earlier==type)) + tmp += "T00:00:00"; + N(tmp.size()==19 || sel_date==type, + F("selector '%s' is not a valid date (%s)") % sel % tmp); + + if (sel != tmp) + { + P (F ("expanded date '%s' -> '%s'\n") % sel % tmp); + sel = tmp; + } + if (sel_date == type && sel.size() < 19) + sel = string("*") + sel + "*"; // to be GLOBbed later + break; + + case sel_branch: + case sel_head: + if (sel.empty()) + { + string msg = (sel_branch == type + ? F("the empty branch selector b: refers to " + "the current branch") + : F("the empty head selector h: refers to " + "the head of the current branch") + ).str(); + app.require_workspace(msg); + sel = app.opts.branchname(); + } + break; + + case sel_cert: + N(!sel.empty(), + F("the cert selector c: may not be empty")); + break; + + default: break; + } + } +} + +static void +parse_selector(string const & str, selector_list & sels, + app_state & app) +{ + sels.clear(); + + // this rule should always be enabled, even if the user specifies + // --norc: if you provide a revision id, you get a revision id. + if (str.find_first_not_of(constants::legal_id_bytes) == string::npos + && str.size() == constants::idlen) + { + sels.push_back(make_pair(sel_ident, str)); + } + else + { + typedef boost::tokenizer > tokenizer; + boost::escaped_list_separator slash("\\", "/", ""); + tokenizer tokens(str, slash); + + vector selector_strings; + copy(tokens.begin(), tokens.end(), back_inserter(selector_strings)); + + for (vector::const_iterator i = selector_strings.begin(); + i != selector_strings.end(); ++i) + { + string sel; + selector_type type = sel_unknown; + + decode_selector(*i, type, sel, app); + sels.push_back(make_pair(type, sel)); + } + } +} + +static void +complete_one_selector(selector_type ty, string const & value, + set & completions, + database & db, project_t & project) +{ + switch (ty) + { + case sel_ident: + db.complete(value, completions); + break; + + case sel_parent: + db.select_parent(value, completions); + break; + + case sel_author: + db.select_cert(author_cert_name(), value, completions); + break; + + case sel_tag: + db.select_cert(tag_cert_name(), value, completions); + break; + + case sel_branch: + I(!value.empty()); + db.select_cert(branch_cert_name(), value, completions); + break; + + case sel_unknown: + db.select_author_tag_or_branch(value, completions); + break; + + case sel_date: + db.select_date(value, "GLOB", completions); + break; + + case sel_earlier: + db.select_date(value, "<=", completions); + break; + + case sel_later: + db.select_date(value, ">", completions); + break; + + case sel_cert: { - if (!db.hook_expand_selector(sel, tmp)) + I(!value.empty()); + size_t spot = value.find("="); + + if (spot != (size_t)-1) { - L(FL("expansion of selector '%s' failed") % sel); + string certname; + string certvalue; + + certname = value.substr(0, spot); + spot++; + certvalue = value.substr(spot); + + db.select_cert(certname, certvalue, completions); } else - { - P(F("expanded selector '%s' -> '%s'") % sel % tmp); - sel = tmp; - } + db.select_cert(value, completions); } + break; - if (sel.size() >= 2 && sel[1] == ':') + case sel_head: { - switch (sel[0]) - { - case 'a': - type = sel_author; - break; - case 'b': - type = sel_branch; - break; - case 'h': - type = sel_head; - break; - case 'd': - type = sel_date; - break; - case 'i': - type = sel_ident; - break; - case 't': - type = sel_tag; - break; - case 'c': - type = sel_cert; - break; - case 'l': - type = sel_later; - break; - case 'e': - type = sel_earlier; - break; - case 'p': - type = sel_parent; - break; - default: - W(F("unknown selector type: %c") % sel[0]); - break; - } - sel.erase(0,2); + // get branch names + set branch_names; + I(!value.empty()); + project.get_branch_list(globish(value), branch_names, true); - /* a selector date-related should be validated */ - if (sel_date==type || sel_later==type || sel_earlier==type) - { - if (db.hook_exists("expand_date")) - { - N(db.hook_expand_date(sel, tmp), - F("selector '%s' is not a valid date\n") % sel); - } - else - { - // if expand_date is not available, start with something - tmp = sel; - } + L(FL("found %d matching branches") % branch_names.size()); - // if we still have a too short datetime string, expand it with - // default values, but only if the type is earlier or later; - // for searching a specific date cert this makes no sense - // FIXME: this is highly speculative if expand_date wasn't called - // beforehand - tmp could be _anything_ but a partial date string - if (tmp.size()<8 && (sel_later==type || sel_earlier==type)) - tmp += "-01T00:00:00"; - else if (tmp.size()<11 && (sel_later==type || sel_earlier==type)) - tmp += "T00:00:00"; - N(tmp.size()==19 || sel_date==type, - F("selector '%s' is not a valid date (%s)") % sel % tmp); - - if (sel != tmp) - { - P (F ("expanded date '%s' -> '%s'\n") % sel % tmp); - sel = tmp; - } + // for each branch name, get the branch heads + for (set::const_iterator bn = branch_names.begin(); + bn != branch_names.end(); bn++) + { + set branch_heads; + project.get_branch_heads(*bn, branch_heads); + completions.insert(branch_heads.begin(), branch_heads.end()); + L(FL("after get_branch_heads for %s, heads has %d entries") + % (*bn) % completions.size()); } } - } + break; + } +} - void - complete_selector(string const & orig_sel, - vector > const & limit, - selector_type & type, - set & completions, - database & db) - { - string sel; - decode_selector(orig_sel, type, sel, db); - db.complete(type, sel, limit, completions); - } +static void +complete_selector(selector_list const & limit, + set & completions, + database & db, project_t & project) +{ + if (limit.empty()) // all the ids in the database + { + db.complete("", completions); + return; + } - vector > - parse_selector(string const & str, - database & db) - { - vector > sels; + selector_list::const_iterator i = limit.begin(); + complete_one_selector(i->first, i->second, completions, db, project); + i++; - // this rule should always be enabled, even if the user specifies - // --norc: if you provide a revision id, you get a revision id. - if (str.find_first_not_of(constants::legal_id_bytes) == string::npos - && str.size() == constants::idlen) - { - sels.push_back(make_pair(sel_ident, str)); - } - else - { - typedef boost::tokenizer > tokenizer; - boost::escaped_list_separator slash("\\", "/", ""); - tokenizer tokens(str, slash); + while (i != limit.end()) + { + set candidates; + set intersection; + complete_one_selector(i->first, i->second, candidates, db, project); - vector selector_strings; - copy(tokens.begin(), tokens.end(), back_inserter(selector_strings)); + intersection.clear(); + set_intersection(completions.begin(), completions.end(), + candidates.begin(), candidates.end(), + inserter(intersection, intersection.end())); - for (vector::const_iterator i = selector_strings.begin(); - i != selector_strings.end(); ++i) - { - string sel; - selector_type type = sel_unknown; + completions = intersection; + i++; + } +} - decode_selector(*i, type, sel, db); - sels.push_back(make_pair(type, sel)); - } - } +void +complete(app_state & app, + string const & str, + set & completions) +{ + selector_list sels; + parse_selector(str, sels, app); - return sels; - } + // avoid logging if there's no expansion to be done + if (sels.size() == 1 + && sels[0].first == sel_ident + && sels[0].second.size() == constants::idlen) + { + completions.insert(revision_id(sels[0].second)); + N(app.db.revision_exists(*completions.begin()), + F("no such revision '%s'") % *completions.begin()); + return; + } -}; // namespace selectors + P(F("expanding selection '%s'") % str); + complete_selector(sels, completions, app.db, app.get_project()); + N(completions.size() != 0, + F("no match for selection '%s'") % str); + + for (set::const_iterator i = completions.begin(); + i != completions.end(); ++i) + { + P(F("expanded to '%s'") % *i); + + // This may be impossible, but let's make sure. + // All the callers used to do it. + N(app.db.revision_exists(*i), + F("no such revision '%s'") % *i); + } +} + +void +complete(app_state & app, + string const & str, + revision_id & completion) +{ + set completions; + + complete(app, str, completions); + + I(completions.size() > 0); + diagnose_ambiguous_expansion(app, str, completions); + + completion = *completions.begin(); +} + + +void +expand_selector(app_state & app, + string const & str, + set & completions) +{ + selector_list sels; + parse_selector(str, sels, app); + + // avoid logging if there's no expansion to be done + if (sels.size() == 1 + && sels[0].first == sel_ident + && sels[0].second.size() == constants::idlen) + { + completions.insert(revision_id(sels[0].second)); + return; + } + + complete_selector(sels, completions, app.db, app.get_project()); +} + +void +diagnose_ambiguous_expansion(app_state & app, + string const & str, + set const & completions) +{ + if (completions.size() <= 1) + return; + + string err = (F("selection '%s' has multiple ambiguous expansions:") + % str).str(); + for (set::const_iterator i = completions.begin(); + i != completions.end(); ++i) + err += ("\n" + describe_revision(app.db, app.get_project(), *i)); + + N(false, i18n_format(err)); +} + + // Local Variables: // mode: C++ // fill-column: 76 ============================================================ --- selectors.hh 8403b2915609ef93a8287a336ba4b59262563c1a +++ selectors.hh d717061d1740519d27e3381fd91e46ce032dfbed @@ -10,43 +10,35 @@ // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. -#include "vector.hh" -#include +#include "vocab.hh" #include -class database; +class app_state; -namespace selectors -{ +// In the normal case, to expand a selector on the command line, use one of +// these functions: the former if the selector can legitimately expand to +// more than one revision, the latter if it shouldn't. Both treat a +// selector that expands to zero revisions, or a nonexistent revision, as an +// usage error, and generate progress messages when expanding selectors. - typedef enum - { - sel_author, - sel_branch, - sel_head, - sel_date, - sel_tag, - sel_ident, - sel_cert, - sel_earlier, - sel_later, - sel_parent, - sel_unknown - } - selector_type; +void complete(app_state & app, std::string const & str, + std::set & completions); - void - complete_selector(std::string const & orig_sel, - std::vector > const & limit, - selector_type & type, - std::set & completions, - database & db); - std::vector > - parse_selector(std::string const & str, - database & db); +void complete(app_state & app, std::string const & str, + revision_id & completion); -}; // namespace selectors +// For extra control, use these functions. expand_selector is just like the +// first overload of complete() except that it produces no progress messages +// or usage errors. diagnose_ambiguous_expansion generates the canonical +// usage error if the set it is handed has more than one element. +void expand_selector(app_state & app, std::string const & str, + std::set & completions); + +void diagnose_ambiguous_expansion(app_state & app, std::string const & str, + std::set const & completions); + + // Local Variables: // mode: C++ // fill-column: 76