# # add_file "options.hh" # # patch "ChangeLog" # from [1ca0c140585eb24f3192496cb9194d1cde27c84b] # to [3fc8d9c3d1147a8a769762b8a75e3ab171f87dd6] # # patch "commands.cc" # from [42a9854e615fff7b96075632e07cb88a6d12ae29] # to [b049d01f7ac06a0d91533b09248110209b37b52c] # # patch "commands.hh" # from [f2f45ab20f37d33f52054ae343a0b1a099494a35] # to [88e2abf45cac40d7427a2c657ff1be5d5d7705c6] # # patch "monotone.cc" # from [7b86d611ebb4f89b6fb733b5e322bd500302d020] # to [0b61014f912a7e1a9fea5022edbefe8f5efad585] # # patch "options.hh" # from [] # to [09fb6eac3b7dd6930f3fc7f564e3e3ef3cc16a79] # # patch "tests/t_automate_heads.at" # from [4c9ac3371e007c84364f5b48004cb4f4fe5604c8] # to [7f7f54a5b2003ee87db7266b7979a98163df0891] # # patch "tests/t_sticky_branch.at" # from [a54a5cc4d95a7d7b47b4d4458e26868c28dad48e] # to [0c2988980ecf63dc282283f17ad783839f18e0ca] # # patch "tests/t_update_missing.at" # from [e0dbf59a244cae8582b3a34c1f53418b1eceb869] # to [9fe6a573758f9e4659a018284aacb77ea3b0805b] # --- ChangeLog +++ ChangeLog @@ -1,3 +1,41 @@ +2005-04-23 Richard Levitte + + * monotone.cc, options.hh: Move the option numbers to options.hh, + so they can be easily retrieved by other modules. + * monotone.cc: split the options table in global options and + command specific options. The former are always understood, while + the latter are only understood by the commands that declare it + (see below). + (my_poptStuffArgFile): There's no need to keep a copy of the + stuffed argv. This was really never a problem. + (coption_string): New function to find the option string from an + option number. + (cpp_main): Keep track of which command-specific options were + given, and check that the given command really uses them. Make + sure that when the help is written, only the appropriate command- + specific options are shown. We do this by hacking the command- + specific options table. + Throw away sub_argvs, as it's not needed any more (and realy never + was). + + * commands.cc: Include options.hh to get the option numbers. + (commands_ops): New structure to hold the option + numbers used by a command. + (commands): Use it. + (command_options): Function to get the set of command-specific + options for a specific command. + (CMD): Changed to take a new parameter describing which command- + specific options this command takes. Note that for commands that + do not take command-specific options, this new parameter must + still be given, just left empty. + Update all commands with this new parameter. + * commands.hh: Declare command_options. + + * tests/t_automate_heads.at: 'automate heads' never used the value + of --branch. + * tests/t_sticky_branch.at: and neither did 'log'... + * tests/t_update_missing.at: nor did 'add'... + 2005-04-21 Jeremy Cowgar * tests/t_multiple_heads_msg.at: Now checks to ensure 'multiple head' --- commands.cc +++ commands.cc @@ -43,6 +43,7 @@ #include "automate.hh" #include "inodeprint.hh" #include "platform.hh" +#include "options.hh" // // this file defines the task-oriented "top level" commands which can be @@ -80,16 +81,29 @@ static map cmds; + struct command_opts + { + set opts; + command_opts() {} + command_opts & operator%(int o) + { opts.insert(o); return *this; } + command_opts & operator%(command_opts const &o) + { opts.insert(o.opts.begin(), o.opts.end()); return *this; } + }; + struct command { string name; string cmdgroup; string params; string desc; + command_opts options; command(string const & n, string const & g, string const & p, - string const & d) : name(n), cmdgroup(g), params(p), desc(d) + string const & d, + command_opts const & o) + : name(n), cmdgroup(g), params(p), desc(d), options(o) { cmds[n] = this; } virtual ~command() {} virtual void exec(app_state & app, vector const & args) = 0; @@ -222,26 +236,42 @@ } } -#define CMD(C, group, params, desc) \ -struct cmd_ ## C : public command \ -{ \ - cmd_ ## C() : command(#C, group, params, desc) \ - {} \ - virtual void exec(app_state & app, \ - vector const & args); \ -}; \ -static cmd_ ## C C ## _cmd; \ -void cmd_ ## C::exec(app_state & app, \ - vector const & args) \ + set command_options(string const & cmd) + { + string completed = complete_command(cmd); + + if (cmds.find(completed) != cmds.end()) + { + return cmds[completed]->options.opts; + } + else + { + return set(); + } + } -#define ALIAS(C, realcommand) \ -CMD(C, realcommand##_cmd.cmdgroup, realcommand##_cmd.params, \ - realcommand##_cmd.desc + "\nAlias for " #realcommand) \ -{ \ - process(app, string(#realcommand), args); \ +#define CMD(C, group, params, desc, opts) \ +struct cmd_ ## C : public command \ +{ \ + cmd_ ## C() : command(#C, group, params, desc, \ + command_opts() opts) \ + {} \ + virtual void exec(app_state & app, \ + vector const & args); \ +}; \ +static cmd_ ## C C ## _cmd; \ +void cmd_ ## C::exec(app_state & app, \ + vector const & args) \ + +#define ALIAS(C, realcommand) \ +CMD(C, realcommand##_cmd.cmdgroup, realcommand##_cmd.params, \ + realcommand##_cmd.desc + "\nAlias for " #realcommand, \ + % realcommand##_cmd.options) \ +{ \ + process(app, string(#realcommand), args); \ } -static void +static void get_work_path(local_path & w_path) { w_path = (mkpath(book_keeping_dir) / mkpath(work_file_name)).string(); @@ -1175,7 +1205,8 @@ #undef PRINT_INDENTED_SET } -CMD(genkey, "key and cert", "KEYID", "generate an RSA key-pair") +CMD(genkey, "key and cert", "KEYID", "generate an RSA key-pair", + % OPT_ROOT % OPT_DB_NAME % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (args.size() != 1) throw usage(name); @@ -1197,7 +1228,8 @@ guard.commit(); } -CMD(dropkey, "key and cert", "KEYID", "drop a public and private key") +CMD(dropkey, "key and cert", "KEYID", "drop a public and private key", + % OPT_ROOT % OPT_DB_NAME) { bool key_deleted = false; @@ -1229,7 +1261,8 @@ guard.commit(); } -CMD(chkeypass, "key and cert", "KEYID", "change passphrase of a private RSA key") +CMD(chkeypass, "key and cert", "KEYID", "change passphrase of a private RSA key", + % OPT_ROOT % OPT_DB_NAME % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (args.size() != 1) throw usage(name); @@ -1252,7 +1285,8 @@ } CMD(cert, "key and cert", "REVISION CERTNAME [CERTVAL]", - "create a cert for a revision") + "create a cert for a revision", + % OPT_ROOT % OPT_DB_NAME) { if ((args.size() != 3) && (args.size() != 2)) throw usage(name); @@ -1293,7 +1327,8 @@ CMD(trusted, "key and cert", "REVISION NAME VALUE SIGNER1 [SIGNER2 [...]]", "test whether a hypothetical cert would be trusted\n" - "by current settings") + "by current settings", + % OPT_ROOT % OPT_DB_NAME % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (args.size() < 4) throw usage(name); @@ -1330,7 +1365,8 @@ } CMD(tag, "review", "REVISION TAGNAME", - "put a symbolic tag cert on a revision version") + "put a symbolic tag cert on a revision version", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 2) throw usage(name); @@ -1343,7 +1379,8 @@ CMD(testresult, "review", "ID (pass|fail|true|false|yes|no|1|0)", - "note the results of running a test on a revision") + "note the results of running a test on a revision", + % OPT_ROOT % OPT_DB_NAME ) { if (args.size() != 2) throw usage(name); @@ -1355,7 +1392,8 @@ } CMD(approve, "review", "REVISION", - "approve of a particular revision") + "approve of a particular revision", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME) { if (args.size() != 1) throw usage(name); @@ -1371,7 +1409,8 @@ CMD(disapprove, "review", "REVISION", - "disapprove of a particular revision") + "disapprove of a particular revision", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME) { if (args.size() != 1) throw usage(name); @@ -1416,7 +1455,8 @@ } CMD(comment, "review", "REVISION [COMMENT]", - "comment on a particular revision") + "comment on a particular revision", + % OPT_ROOT % OPT_DB_NAME % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (args.size() != 1 && args.size() != 2) throw usage(name); @@ -1439,7 +1479,8 @@ -CMD(add, "working copy", "PATH...", "add files to working copy") +CMD(add, "working copy", "PATH...", "add files to working copy", + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (args.size() < 1) throw usage(name); @@ -1463,7 +1504,8 @@ update_any_attrs(app); } -CMD(drop, "working copy", "PATH...", "drop files from working copy") +CMD(drop, "working copy", "PATH...", "drop files from working copy", + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (args.size() < 1) throw usage(name); @@ -1488,7 +1530,8 @@ } -CMD(rename, "working copy", "SRC DST", "rename entries in the working copy") +CMD(rename, "working copy", "SRC DST", "rename entries in the working copy", + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (args.size() != 2) throw usage(name); @@ -1514,7 +1557,8 @@ // (such as automated processes might want to do). CMD(fcommit, "tree", "REVISION FILENAME [LOG_MESSAGE]", - "commit change to a single file") + "commit change to a single file", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 2 && args.size() != 3) throw usage(name); @@ -1596,7 +1640,8 @@ } -CMD(fload, "debug", "", "load file contents into db") +CMD(fload, "debug", "", "load file contents into db", + % OPT_ROOT % OPT_DB_NAME) { string s = get_stdin(); @@ -1609,7 +1654,8 @@ dbw.consume_file_data(f_id, f_data); } -CMD(fmerge, "debug", " ", "merge 3 files and output result") +CMD(fmerge, "debug", " ", "merge 3 files and output result", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 3) throw usage(name); @@ -1640,7 +1686,8 @@ } -CMD(status, "informative", "[PATH]...", "show status of working copy") +CMD(status, "informative", "[PATH]...", "show status of working copy", + % OPT_ROOT % OPT_DB_NAME) { revision_set rs; manifest_map m_old, m_new; @@ -1655,7 +1702,8 @@ } CMD(identify, "working copy", "[PATH]", - "calculate identity of PATH or stdin") + "calculate identity of PATH or stdin", + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (!(args.size() == 0 || args.size() == 1)) throw usage(name); @@ -1679,7 +1727,8 @@ CMD(cat, "informative", "(file|manifest|revision) [ID]\n" "file REVISION FILENAME", - "write file, manifest, or revision from database to stdout") + "write file, manifest, or revision from database to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() < 1 || args.size() > 3) throw usage(name); @@ -1781,7 +1830,9 @@ CMD(checkout, "tree", "REVISION DIRECTORY\nDIRECTORY\n", - "check out revision from database into directory") + "check out revision from database into directory", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { revision_id ident; @@ -1875,7 +1926,8 @@ ALIAS(co, checkout) -CMD(heads, "tree", "", "show unmerged head revisions of branch") +CMD(heads, "tree", "", "show unmerged head revisions of branch", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME) { set heads; if (args.size() != 0) @@ -2095,7 +2147,8 @@ "ignored\n" "missing", "show database objects, or the current working copy manifest,\n" - "or unknown, intentionally ignored, or missing state files") + "or unknown, intentionally ignored, or missing state files", + % OPT_ROOT % OPT_DB_NAME % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { if (args.size() == 0) throw usage(name); @@ -2130,7 +2183,8 @@ ALIAS(ls, list) -CMD(mdelta, "packet i/o", "OLDID NEWID", "write manifest delta packet to stdout") +CMD(mdelta, "packet i/o", "OLDID NEWID", "write manifest delta packet to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 2) throw usage(name); @@ -2154,7 +2208,8 @@ manifest_delta(del)); } -CMD(fdelta, "packet i/o", "OLDID NEWID", "write file delta packet to stdout") +CMD(fdelta, "packet i/o", "OLDID NEWID", "write file delta packet to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 2) throw usage(name); @@ -2176,7 +2231,8 @@ pw.consume_file_delta(f_old_id, f_new_id, file_delta(del)); } -CMD(rdata, "packet i/o", "ID", "write revision data packet to stdout") +CMD(rdata, "packet i/o", "ID", "write revision data packet to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 1) throw usage(name); @@ -2193,7 +2249,8 @@ pw.consume_revision_data(r_id, r_data); } -CMD(mdata, "packet i/o", "ID", "write manifest data packet to stdout") +CMD(mdata, "packet i/o", "ID", "write manifest data packet to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 1) throw usage(name); @@ -2211,7 +2268,8 @@ } -CMD(fdata, "packet i/o", "ID", "write file data packet to stdout") +CMD(fdata, "packet i/o", "ID", "write file data packet to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 1) throw usage(name); @@ -2229,7 +2287,8 @@ } -CMD(certs, "packet i/o", "ID", "write cert packets to stdout") +CMD(certs, "packet i/o", "ID", "write cert packets to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 1) throw usage(name); @@ -2246,7 +2305,8 @@ pw.consume_revision_cert(idx(certs, i)); } -CMD(pubkey, "packet i/o", "ID", "write public key packet to stdout") +CMD(pubkey, "packet i/o", "ID", "write public key packet to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 1) throw usage(name); @@ -2261,7 +2321,8 @@ pw.consume_public_key(ident, key); } -CMD(privkey, "packet i/o", "ID", "write private key packet to stdout") +CMD(privkey, "packet i/o", "ID", "write private key packet to stdout", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 1) throw usage(name); @@ -2277,7 +2338,8 @@ } -CMD(read, "packet i/o", "", "read packets from stdin") +CMD(read, "packet i/o", "", "read packets from stdin", + % OPT_ROOT % OPT_DB_NAME) { packet_db_writer dbw(app, true); size_t count = read_packets(cin, dbw); @@ -2290,7 +2352,8 @@ CMD(reindex, "network", "", - "rebuild the indices used to sync over the network") + "rebuild the indices used to sync over the network", + % OPT_ROOT % OPT_DB_NAME % OPT_TICKER) { if (args.size() > 0) throw usage(name); @@ -2359,7 +2422,8 @@ } CMD(push, "network", "[ADDRESS[:PORTNUMBER] [COLLECTION]]", - "push COLLECTION to netsync server at ADDRESS") + "push COLLECTION to netsync server at ADDRESS", + % OPT_ROOT % OPT_DB_NAME % OPT_KEY_NAME % OPT_TICKER) { utf8 addr; vector collections; @@ -2373,7 +2437,8 @@ } CMD(pull, "network", "[ADDRESS[:PORTNUMBER] [COLLECTION]]", - "pull COLLECTION from netsync server at ADDRESS") + "pull COLLECTION from netsync server at ADDRESS", + % OPT_ROOT % OPT_DB_NAME % OPT_KEY_NAME % OPT_TICKER) { utf8 addr; vector collections; @@ -2386,7 +2451,8 @@ } CMD(sync, "network", "[ADDRESS[:PORTNUMBER] [COLLECTION]]", - "sync COLLECTION with netsync server at ADDRESS") + "sync COLLECTION with netsync server at ADDRESS", + % OPT_ROOT % OPT_DB_NAME % OPT_KEY_NAME % OPT_TICKER) { utf8 addr; vector collections; @@ -2400,7 +2466,8 @@ } CMD(serve, "network", "ADDRESS[:PORTNUMBER] COLLECTION...", - "listen on ADDRESS and serve COLLECTION to connecting clients") + "listen on ADDRESS and serve COLLECTION to connecting clients", + % OPT_ROOT % OPT_DB_NAME % OPT_KEY_NAME % OPT_NORC % OPT_NOSTD % OPT_RCFILE) { if (args.size() < 2) throw usage(name); @@ -2432,7 +2499,8 @@ "changesetify\n" "rebuild\n" "set_epoch BRANCH EPOCH\n", - "manipulate database state") + "manipulate database state", + % OPT_ROOT % OPT_DB_NAME % OPT_TICKER) { if (args.size() == 1) { @@ -2481,7 +2549,7 @@ } CMD(attr, "working copy", "set FILE ATTR VALUE\nget FILE [ATTR]\ndrop FILE", - "set, get or drop file attributes") + "set, get or drop file attributes",) { if (args.size() < 2 || args.size() > 4) throw usage(name); @@ -2606,8 +2674,10 @@ I(false); } -CMD(commit, "working copy", "[--message=STRING] [PATH]...", - "commit working copy to database") +CMD(commit, "working copy", "[PATH]...", + "commit working copy to database", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME % OPT_MESSAGE % OPT_DATE % OPT_AUTHOR + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { string log_message(""); revision_set rs; @@ -2652,11 +2722,11 @@ N(log_message.find_first_not_of(" \r\t\n") != string::npos, F("empty log message")); - + transaction_guard guard(app.db); { packet_db_writer dbw(app); - + if (app.db.revision_exists(rid)) { W(F("revision %s already in database\n") % rid); @@ -2665,11 +2735,11 @@ { // new revision L(F("inserting new revision %s\n") % rid); - + I(rs.edges.size() == 1); edge_map::const_iterator edge = rs.edges.begin(); I(edge != rs.edges.end()); - + // process manifest delta or new manifest if (app.db.manifest_version_exists(rs.new_manifest)) { @@ -2677,13 +2747,13 @@ } else if (app.db.manifest_version_exists(edge_old_manifest(edge))) { - L(F("inserting manifest delta %s -> %s\n") - % edge_old_manifest(edge) + L(F("inserting manifest delta %s -> %s\n") + % edge_old_manifest(edge) % rs.new_manifest); delta del; diff(m_old, m_new, del); - dbw.consume_manifest_delta(edge_old_manifest(edge), - rs.new_manifest, + dbw.consume_manifest_delta(edge_old_manifest(edge), + rs.new_manifest, manifest_delta(del)); } else @@ -2693,7 +2763,7 @@ write_manifest_map(m_new, m_new_data); dbw.consume_manifest_data(rs.new_manifest, m_new_data); } - + // process file deltas or new files for (change_set::delta_map::const_iterator i = edge_changes(edge).deltas.begin(); i != edge_changes(edge).deltas.end(); ++i) @@ -2701,13 +2771,13 @@ if (! delta_entry_src(i).inner()().empty() && app.db.file_version_exists(delta_entry_dst(i))) { - L(F("skipping file delta %s, already in database\n") + L(F("skipping file delta %s, already in database\n") % delta_entry_dst(i)); } else if (! delta_entry_src(i).inner()().empty() && app.db.file_version_exists(delta_entry_src(i))) { - L(F("inserting delta %s -> %s\n") + L(F("inserting delta %s -> %s\n") % delta_entry_src(i) % delta_entry_dst(i)); file_data old_data; data new_data; @@ -2721,7 +2791,7 @@ % delta_entry_path(i)); delta del; diff(old_data.inner(), new_data, del); - dbw.consume_file_delta(delta_entry_src(i), + dbw.consume_file_delta(delta_entry_src(i), delta_entry_dst(i), file_delta(del)); } @@ -2820,7 +2890,7 @@ } else { - read_localized_data(delta_entry_path(i), + read_localized_data(delta_entry_path(i), unpacked, app.lua); } @@ -3030,18 +3100,23 @@ } CMD(cdiff, "informative", "[--revision=REVISION [--revision=REVISION]] [PATH]...", - "show current context diffs on stdout") + "show current context diffs on stdout", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME % OPT_REVISION + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { do_diff(name, app, args, context_diff); } CMD(diff, "informative", "[--revision=REVISION [--revision=REVISION]] [PATH]...", - "show current unified diffs on stdout") + "show current unified diffs on stdout", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME % OPT_REVISION + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { do_diff(name, app, args, unified_diff); } -CMD(lca, "debug", "LEFT RIGHT", "print least common ancestor") +CMD(lca, "debug", "LEFT RIGHT", "print least common ancestor", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 2) throw usage(name); @@ -3058,7 +3133,8 @@ } -CMD(lcad, "debug", "LEFT RIGHT", "print least common ancestor / dominator") +CMD(lcad, "debug", "LEFT RIGHT", "print least common ancestor / dominator", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 2) throw usage(name); @@ -3075,7 +3151,8 @@ } -CMD(agraph, "debug", "", "dump ancestry graph to stdout in VCG format") +CMD(agraph, "debug", "", "dump ancestry graph to stdout in VCG format", + % OPT_ROOT % OPT_DB_NAME) { set nodes; multimap branches; @@ -3180,7 +3257,9 @@ // cout << "change set '" << name << "'\n" << dat << endl; // } -CMD(update, "working copy", "\nREVISION", "update working copy to be based off another revision") +CMD(update, "working copy", "\nREVISION", "update working copy to be based off another revision", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME + % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { manifest_map m_old, m_ancestor, m_working, m_chosen; manifest_id m_ancestor_id, m_chosen_id; @@ -3422,7 +3501,8 @@ } -CMD(merge, "tree", "", "merge unmerged heads of branch") +CMD(merge, "tree", "", "merge unmerged heads of branch", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME) { set heads; @@ -3471,7 +3551,8 @@ } CMD(propagate, "tree", "SOURCE-BRANCH DEST-BRANCH", - "merge from one branch to another asymmetrically") + "merge from one branch to another asymmetrically", + % OPT_ROOT % OPT_DB_NAME) { // this is a special merge operator, but very useful for people maintaining // "slightly disparate but related" trees. it does a one-way merge; less @@ -3554,7 +3635,8 @@ } CMD(explicit_merge, "tree", "LEFT-REVISION RIGHT-REVISION DEST-BRANCH\nLEFT-REVISION RIGHT-REVISION COMMON-ANCESTOR DEST-BRANCH", - "merge two explicitly given revisions, placing result in given branch") + "merge two explicitly given revisions, placing result in given branch", + % OPT_ROOT % OPT_DB_NAME) { revision_id left, right, ancestor; string branch; @@ -3609,7 +3691,9 @@ P(F("[merged] %s\n") % merged); } -CMD(complete, "informative", "(revision|manifest|file) PARTIAL-ID", "complete partial id") +CMD(complete, "informative", "(revision|manifest|file) PARTIAL-ID", + "complete partial id", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 2) throw usage(name); @@ -3650,7 +3734,8 @@ CMD(revert, "working copy", "[PATH]...", - "revert file(s), dir(s) or entire working copy") + "revert file(s), dir(s) or entire working copy", + % OPT_ROOT % OPT_DB_NAME % OPT_NOSTD % OPT_NORC % OPT_RCFILE) { manifest_map m_old; revision_id old_revision_id; @@ -3711,7 +3796,8 @@ CMD(rcs_import, "debug", "RCSFILE...", "import all versions in RCS files\n" - "this command doesn't reconstruct revisions. you probably want cvs_import") + "this command doesn't reconstruct revisions. you probably want cvs_import", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME) { if (args.size() < 1) throw usage(name); @@ -3726,7 +3812,8 @@ } -CMD(cvs_import, "rcs", "CVSROOT", "import all versions in CVS repository") +CMD(cvs_import, "rcs", "CVSROOT", "import all versions in CVS repository", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME) { if (args.size() != 1) throw usage(name); @@ -3755,7 +3842,8 @@ } } -CMD(log, "informative", "[ID] [file]", "print history in reverse order starting from 'ID' (filtering by 'file')") +CMD(log, "informative", "[ID] [file]", "print history in reverse order starting from 'ID' (filtering by 'file')", + % OPT_ROOT % OPT_DB_NAME % OPT_DEPTH) { revision_set rev; revision_id rid; @@ -3903,7 +3991,8 @@ } -CMD(setup, "tree", "DIRECTORY", "setup a new working copy directory") +CMD(setup, "tree", "DIRECTORY", "setup a new working copy directory", + % OPT_ROOT % OPT_DB_NAME % OPT_BRANCH_NAME % OPT_KEY_NAME) { string dir; @@ -3925,7 +4014,8 @@ "toposort [REV1 [REV2 [REV3 [...]]]]\n" "ancestry_difference NEW_REV [OLD_REV1 [OLD_REV2 [...]]]\n" "leaves", - "automation interface") + "automation interface", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() == 0) throw usage(name); @@ -3939,7 +4029,8 @@ } CMD(set, "vars", "DOMAIN NAME VALUE", - "set the database variable NAME to VALUE, in domain DOMAIN") + "set the database variable NAME to VALUE, in domain DOMAIN", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 3) throw usage(name); @@ -3954,7 +4045,8 @@ } CMD(unset, "vars", "DOMAIN NAME", - "remove the database variable NAME in domain DOMAIN") + "remove the database variable NAME in domain DOMAIN", + % OPT_ROOT % OPT_DB_NAME) { if (args.size() != 2) throw usage(name); --- commands.hh +++ commands.hh @@ -25,6 +25,7 @@ namespace commands { void explain_usage(std::string const & cmd, std::ostream & out); int process(app_state & app, std::string const & cmd, std::vector const & args); + std::set command_options(std::string const & cmd); typedef enum { sel_author, --- monotone.cc +++ monotone.cc @@ -26,28 +26,8 @@ #include "transforms.hh" #include "ui.hh" #include "mt_version.hh" +#include "options.hh" -#define OPT_DEBUG 1 -#define OPT_HELP 2 -#define OPT_NOSTD 3 -#define OPT_NORC 4 -#define OPT_RCFILE 5 -#define OPT_DB_NAME 6 -#define OPT_KEY_NAME 7 -#define OPT_BRANCH_NAME 8 -#define OPT_QUIET 9 -#define OPT_VERSION 10 -#define OPT_DUMP 11 -#define OPT_TICKER 12 -#define OPT_FULL_VERSION 13 -#define OPT_REVISION 14 -#define OPT_MESSAGE 15 -#define OPT_ROOT 16 -#define OPT_DEPTH 17 -#define OPT_ARGFILE 18 -#define OPT_DATE 19 -#define OPT_AUTHOR 20 - // main option processing and exception handling code using namespace std; @@ -55,28 +35,44 @@ char * argstr = NULL; long arglong = 0; +// Options are divide into two tables. The first one is command-specific +// options (hence the `c' in `coptions'). The second is the global one +// with options that aren't tied to specific commands. + +struct poptOption coptions[] = + { + {"branch", 'b', POPT_ARG_STRING, &argstr, OPT_BRANCH_NAME, "select branch cert for operation", NULL}, + {"ticker", 0, POPT_ARG_STRING, &argstr, OPT_TICKER, "set ticker style (count|dot|none) [count]", NULL}, + {"revision", 'r', POPT_ARG_STRING, &argstr, OPT_REVISION, "select revision id for operation", NULL}, + {"message", 'm', POPT_ARG_STRING, &argstr, OPT_MESSAGE, "set commit changelog message", NULL}, + {"date", 0, POPT_ARG_STRING, &argstr, OPT_DATE, "override date/time for commit", NULL}, + {"author", 0, POPT_ARG_STRING, &argstr, OPT_AUTHOR, "override author for commit", NULL}, + {"depth", 0, POPT_ARG_LONG, &arglong, OPT_DEPTH, "limit the log output to the given number of entries", NULL}, + { NULL, 0, 0, NULL, 0, NULL, NULL } + }; + struct poptOption options[] = { {"debug", 0, POPT_ARG_NONE, NULL, OPT_DEBUG, "print debug log to stderr while running", NULL}, {"dump", 0, POPT_ARG_STRING, &argstr, OPT_DUMP, "file to dump debugging log to, on failure", NULL}, {"quiet", 0, POPT_ARG_NONE, NULL, OPT_QUIET, "suppress log and progress messages", NULL}, {"help", 0, POPT_ARG_NONE, NULL, OPT_HELP, "display help message", NULL}, + {"version", 0, POPT_ARG_NONE, NULL, OPT_VERSION, "print version number, then exit", NULL}, + {"full-version", 0, POPT_ARG_NONE, NULL, OPT_FULL_VERSION, "print detailed version number, then exit", NULL}, + {"xargs", '@', POPT_ARG_STRING, &argstr, OPT_ARGFILE, "insert command line arguments taken from the given file", NULL}, + + // Personally, I think these should be command-specific. However, the + // monotone test suite requires these to be global, it seems... + // -- Richard Levitte {"nostd", 0, POPT_ARG_NONE, NULL, OPT_NOSTD, "do not load standard lua hooks", NULL}, {"norc", 0, POPT_ARG_NONE, NULL, OPT_NORC, "do not load ~/.monotone/monotonerc or MT/monotonerc lua files", NULL}, {"rcfile", 0, POPT_ARG_STRING, &argstr, OPT_RCFILE, "load extra rc file", NULL}, {"key", 'k', POPT_ARG_STRING, &argstr, OPT_KEY_NAME, "set key for signatures", NULL}, {"db", 'd', POPT_ARG_STRING, &argstr, OPT_DB_NAME, "set name of database", NULL}, - {"branch", 'b', POPT_ARG_STRING, &argstr, OPT_BRANCH_NAME, "select branch cert for operation", NULL}, - {"version", 0, POPT_ARG_NONE, NULL, OPT_VERSION, "print version number, then exit", NULL}, - {"full-version", 0, POPT_ARG_NONE, NULL, OPT_FULL_VERSION, "print detailed version number, then exit", NULL}, - {"ticker", 0, POPT_ARG_STRING, &argstr, OPT_TICKER, "set ticker style (count|dot|none) [count]", NULL}, - {"revision", 'r', POPT_ARG_STRING, &argstr, OPT_REVISION, "select revision id for operation", NULL}, - {"message", 'm', POPT_ARG_STRING, &argstr, OPT_MESSAGE, "set commit changelog message", NULL}, - {"date", 0, POPT_ARG_STRING, &argstr, OPT_DATE, "override date/time for commit", NULL}, - {"author", 0, POPT_ARG_STRING, &argstr, OPT_AUTHOR, "override author for commit", NULL}, {"root", 0, POPT_ARG_STRING, &argstr, OPT_ROOT, "limit search for working copy to specified root", NULL}, - {"depth", 0, POPT_ARG_LONG, &arglong, OPT_DEPTH, "limit the log output to the given number of entries", NULL}, - {"xargs", '@', POPT_ARG_STRING, &argstr, OPT_ARGFILE, "insert command line arguments taken from the given file", NULL}, + + // Use the coptions table as well. + { NULL, 0, POPT_ARG_INCLUDE_TABLE, coptions, 0, "Command-specific options", NULL }, { NULL, 0, 0, NULL, 0, NULL, NULL } }; @@ -162,7 +158,7 @@ // Read arguments from a file. The special file '-' means stdin. // Returned value must be free()'d, after arg parsing has completed. -static const char ** +static void my_poptStuffArgFile(poptContext con, utf8 const & filename) { utf8 argstr; @@ -172,7 +168,7 @@ external ext(dat()); system_to_utf8(ext, argstr); } - + const char **argv = 0; int argc = 0; int rc; @@ -192,19 +188,28 @@ F("weird error when stuffing arguments read from %s: %s\n") % filename % poptStrerror(rc)); } - else - { - free(argv); // just in case there was something... - argv = 0; - } - return argv; + free(argv); } +static string +coption_string(int o) +{ + char buf[2] = { 0,0 }; + for(struct poptOption *opt = coptions; opt->val; opt++) + if (o == opt->val) + { + buf[0] = opt->shortName; + return opt->longName + ? string("--") + string(opt->longName) + : string("-") + string(buf); + } + return string(); +} + int cpp_main(int argc, char ** argv) { - clean_shutdown = false; atexit(&dumper); @@ -215,7 +220,7 @@ setlocale(LC_MESSAGES, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); - + { std::ostringstream cmdline_ss; for (int i = 0; i < argc; ++i) @@ -225,42 +230,45 @@ cmdline_ss << "'" << argv[i] << "'"; } L(F("command line: %s\n") % cmdline_ss.str()); - } + } L(F("set locale: LC_CTYPE=%s, LC_MESSAGES=%s\n") % (setlocale(LC_CTYPE, NULL) == NULL ? "n/a" : setlocale(LC_CTYPE, NULL)) % (setlocale(LC_MESSAGES, NULL) == NULL ? "n/a" : setlocale(LC_CTYPE, NULL))); - + // decode all argv values into a UTF-8 array save_initial_path(); utf8_argv uv(argc, argv); // prepare for arg parsing - + cleanup_ptr ctx(poptGetContext(NULL, argc, (char const **) uv.argv, options, 0), &my_poptFreeContext); + set local_options; + for (poptOption *opt = coptions; opt->val; opt++) + local_options.insert(opt->val); + // process main program options int ret = 0; int opt; bool requested_help = false; + set used_local_options; - // keep a list of argv vectors created by get_args_from_file, since they - // must be individually free()'d, but not until arg parsing is done. - std::vector sub_argvs; - poptSetOtherOptionHelp(ctx(), "[OPTION...] command [ARGS...]\n"); try { - app_state app; while ((opt = poptGetNextOpt(ctx())) > 0) { + if (local_options.find(opt) != local_options.end()) + used_local_options.insert(opt); + switch(opt) { case OPT_DEBUG: @@ -345,8 +353,7 @@ break; case OPT_ARGFILE: - sub_argvs.push_back(my_poptStuffArgFile(ctx(), - utf8(string(argstr)))); + my_poptStuffArgFile(ctx(), utf8(string(argstr))); break; case OPT_HELP: @@ -391,22 +398,43 @@ else { string cmd(poptGetArg(ctx())); + + // Make sure the local options used are really used by the + // given command. + set command_options = commands::command_options(cmd); + for (set::const_iterator i = used_local_options.begin(); + i != used_local_options.end(); ++i) + N(command_options.find(*i) != command_options.end(), + F("monotone %s doesn't use the option %s") + % cmd % coption_string(*i)); + vector args; while(poptPeekArg(ctx())) { args.push_back(utf8(string(poptGetArg(ctx())))); } - // we've copied everything we want from the command line into our - // own data structures, so we can finally delete popt's malloc'ed - // argv stuff. - for (std::vector::const_iterator i = sub_argvs.begin(); - i != sub_argvs.end(); ++i) - free(*i); ret = commands::process(app, cmd, args); } } catch (usage & u) { + // Make sure to hide documentation that's not part of + // the current command. + set command_options = commands::command_options(u.which); + for (poptOption *o = coptions; o->val != 0; o++) + { + if (command_options.find(o->val) != command_options.end()) + { + o->argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN; + L(F("Removed 'hidden' from option # %d\n") % o->argInfo); + } + else + { + o->argInfo |= POPT_ARGFLAG_DOC_HIDDEN; + L(F("Added 'hidden' to option # %d\n") % o->argInfo); + } + } + poptPrintHelp(ctx(), stdout, 0); cout << endl; commands::explain_usage(u.which, cout); --- options.hh +++ options.hh @@ -0,0 +1,30 @@ +// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*- +// copyright (C) 2002, 2003 graydon hoare +// copyright (C) 2005 Richard Levitte +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +#include "popt/popt.h" + +#define OPT_DEBUG 1 +#define OPT_HELP 2 +#define OPT_NOSTD 3 +#define OPT_NORC 4 +#define OPT_RCFILE 5 +#define OPT_DB_NAME 6 +#define OPT_KEY_NAME 7 +#define OPT_BRANCH_NAME 8 +#define OPT_QUIET 9 +#define OPT_VERSION 10 +#define OPT_DUMP 11 +#define OPT_TICKER 12 +#define OPT_FULL_VERSION 13 +#define OPT_REVISION 14 +#define OPT_MESSAGE 15 +#define OPT_ROOT 16 +#define OPT_DEPTH 17 +#define OPT_ARGFILE 18 +#define OPT_DATE 19 +#define OPT_AUTHOR 20 + --- tests/t_automate_heads.at +++ tests/t_automate_heads.at @@ -36,7 +36,7 @@ AT_CHECK(sort wanted_heads_unsorted >wanted_heads) -AT_CHECK(MONOTONE --branch=otherbranch automate heads testbranch, [], [stdout], [ignore]) +AT_CHECK(MONOTONE automate heads testbranch, [], [stdout], [ignore]) AT_CHECK(CANONICALISE(stdout)) AT_CHECK(cmp wanted_heads stdout) --- tests/t_sticky_branch.at +++ tests/t_sticky_branch.at @@ -16,7 +16,7 @@ AT_CHECK(cd codir && MONOTONE commit --message=foo, [], [ignore], [ignore]) # log doesn't affect given branch -AT_CHECK(cd codir && MONOTONE log --branch=otherbranch, [], [ignore], [ignore]) +AT_CHECK(cd codir && MONOTONE log, [], [ignore], [ignore]) AT_DATA(codir/foo, [more more ]) AT_CHECK(cd codir && MONOTONE commit --message=foo, [], [ignore], [ignore]) --- tests/t_update_missing.at +++ tests/t_update_missing.at @@ -6,7 +6,7 @@ touch a -AT_CHECK(MONOTONE --branch=a add a, [], [ignore], [ignore]) +AT_CHECK(MONOTONE add a, [], [ignore], [ignore]) AT_CHECK(MONOTONE --branch=a commit --message "commit a", [], [ignore], [ignore]) ROOT_R_SHA=`BASE_REVISION` @@ -15,7 +15,7 @@ mkdir b touch b/c -AT_CHECK(MONOTONE --branch=a add b, [], [ignore], [ignore]) +AT_CHECK(MONOTONE add b, [], [ignore], [ignore]) AT_CHECK(MONOTONE --branch=a commit --message "commit b/c", [], [ignore], [ignore]) PROBE_NODE(a, $ROOT_R_SHA, $ROOT_F_SHA) @@ -24,7 +24,7 @@ rmdir b touch d -AT_CHECK(MONOTONE --branch=a add d, [], [ignore], [ignore]) +AT_CHECK(MONOTONE add d, [], [ignore], [ignore]) AT_CHECK(MONOTONE --branch=a commit --message "commit d", [], [ignore], [ignore]) AT_CHECK(MONOTONE --branch=a merge, [], [ignore], [ignore])