# # # add_dir "tests/add_and_then_drop_file_does_nothing" # # add_dir "tests/addition_of_files_and_directories" # # add_dir "tests/calculation_of_incorrect_unidiffs" # # add_dir "tests/delete_work_file_on_checkout" # # add_dir "tests/drop_missing_and_unknown_files" # # add_dir "tests/merging_adds" # # add_dir "tests/merging_adds_in_unrelated_revisions" # # add_dir "tests/merging_data_in_unrelated_files" # # add_dir "tests/merging_data_in_unrelated_revisions" # # add_dir "tests/revert_file_to_base_revision" # # add_file "tests/add_and_then_drop_file_does_nothing/__driver__.lua" # content [ed74475c0927b2c9c47ab97388d3460fdfd3fe07] # # add_file "tests/addition_of_files_and_directories/__driver__.lua" # content [122cd653d6207b86b5af26699414ae3c3e4b8bce] # # add_file "tests/calculation_of_incorrect_unidiffs/__driver__.lua" # content [3e252f3773737b1c3732d31bd555019b1ef78d53] # # add_file "tests/calculation_of_incorrect_unidiffs/firstfile" # content [2fdc5405a533dfc81e28e02c482ca3d783225d51] # # add_file "tests/calculation_of_incorrect_unidiffs/secondfile" # content [60fd4d4949d680b7ec23f41f610769802a464be1] # # add_file "tests/delete_work_file_on_checkout/__driver__.lua" # content [83bdf136c65453873abd9b44139032aaa27c2eb6] # # add_file "tests/drop_missing_and_unknown_files/__driver__.lua" # content [c13655b9cd05d58f28cf28c410a117cc25b09292] # # add_file "tests/merging_adds/__driver__.lua" # content [dfb2cf3a556ab1477e77e79d064a4b74f2857bea] # # add_file "tests/merging_adds_in_unrelated_revisions/__driver__.lua" # content [3573d2502f0f5b458dc75a54cb976baec7057338] # # add_file "tests/merging_data_in_unrelated_files/__driver__.lua" # content [2675e2d9795e4a10c53ef95c13b23b5040815ac2] # # add_file "tests/merging_data_in_unrelated_files/left" # content [003d966645cd3b96511ccd2c394dc01998da180c] # # add_file "tests/merging_data_in_unrelated_files/right" # content [289d77f035783224b0356a05f749ba0a3b0a3246] # # add_file "tests/merging_data_in_unrelated_revisions/__driver__.lua" # content [162fb9899df051fba839e65d2066fa927fe544db] # # add_file "tests/merging_data_in_unrelated_revisions/left" # content [003d966645cd3b96511ccd2c394dc01998da180c] # # add_file "tests/merging_data_in_unrelated_revisions/right" # content [289d77f035783224b0356a05f749ba0a3b0a3246] # # add_file "tests/revert_file_to_base_revision/__driver__.lua" # content [15e25506eba289749c75a219f1f337092634d79c] # # patch "tester.cc" # from [9d2f465fbe9e75040c0d37959cf23cfa7778502d] # to [eda5f5ddd204e30c47fd9930f13ba19057a73b01] # # patch "tester.lua" # from [0d1895da110aae38a101dd9822188c775051c56b] # to [e466aa71c0adb51e51dacd015d7d54aa003500f0] # # patch "tests/test_hooks.lua" # from [234fbb463da7e4614f0b0b279f21c658e74d3706] # to [ce6008e93ec7c8931517ff0173925418e4c6dd6f] # # patch "testsuite.at" # from [33ef9f80a877e665e9ac8171fb29ad5475178285] # to [d87e4f1992dc6a6a58b5575317b401748758ee6e] # # patch "testsuite.lua" # from [c04000a84ded45f0920c2fee726b5ac6f4831ab4] # to [65bc566aa260fd09e30fbfe41566adfb434d9ea1] # ============================================================ --- tests/add_and_then_drop_file_does_nothing/__driver__.lua ed74475c0927b2c9c47ab97388d3460fdfd3fe07 +++ tests/add_and_then_drop_file_does_nothing/__driver__.lua ed74475c0927b2c9c47ab97388d3460fdfd3fe07 @@ -0,0 +1,12 @@ + +mtn_setup() + +addfile("maude", "the file maude\n") +check(cmd(mtn("drop", "maude")), 0, false, false) +check(cmd(mtn("status")), 0, true) +check(not qgrep("_file", "stdout")) + +addfile("liver", "the file liver\n") +check(cmd(mtn("drop", "liver")), 0, false, false) +check(cmd(mtn("status")), 0, true) +check(not qgrep("_file", "stdout")) ============================================================ --- tests/addition_of_files_and_directories/__driver__.lua 122cd653d6207b86b5af26699414ae3c3e4b8bce +++ tests/addition_of_files_and_directories/__driver__.lua 122cd653d6207b86b5af26699414ae3c3e4b8bce @@ -0,0 +1,84 @@ + +mtn_setup() + +mkdir("dir") +writefile("file0", "file 0\n") +writefile("dir/file1", "file 1\n") +writefile("dir/file2", "file 2\n") + +-- adding a non-existent file should fail + +check(cmd(mtn("add", "foobar")), 1, false, false) + +-- newly added files should appear as such + +check(cmd(mtn("add", "file0")), 0, false, true) +check(qgrep("adding file0", "stderr")) + +check(cmd(mtn("add", "dir")), 0, false, true) +check(qgrep("adding dir/file1", "stderr")) +check(qgrep("adding dir/file2", "stderr")) + +check(cmd(mtn("status")), 0, true) +check(qgrep("file0", "stdout")) +check(qgrep("file1", "stdout")) +check(qgrep("file2", "stdout")) + +commit() + +-- redundant additions should not appear +-- (i.e. they should be ignored) + +check(cmd(mtn("add", "file0")), 0, false, true) +check(qgrep("skipping file0", "stderr")) + +check(cmd(mtn("add", "dir")), 0, false, true) +check(qgrep("skipping dir/file1", "stderr")) +check(qgrep("skipping dir/file2", "stderr")) + +check(cmd(mtn("status")), 0, true) +check(not qgrep("file0", "stdout")) +check(not qgrep("file1", "stdout")) +check(not qgrep("file2", "stdout")) + +-- add --unknown should add any files that ls unknown shows you and not ignored + +writefile("file3", "file 3\n") +--writefile("file4.ignore", "file 4 ignore\n") +writefile("dir/file5", "file 5\n") +writefile("dir/file6.ignore", "file 6\n") +mkdir("dir2") +writefile("dir2/file7", "file 7\n") +--writefile(".mtn-ignore", ".*\\.ignore$\n") + +--check(cmd(raw_mtn("ls", "unkown")), 0, true, false) + +check(cmd(mtn("add", "--unknown")), 0, false, true) +check(qgrep('adding file3', "stderr")) +--check(not qgrep('adding file4.ignore', "stderr")) +check(qgrep('adding dir/file5', "stderr")) +--check(not qgrep('adding dir/file6.ignore', "stderr")) +check(qgrep('adding dir2', "stderr")) +check(qgrep('adding dir2/file7', "stderr")) +check(not qgrep('skipping dir2/file7', "stderr")) +check(not qgrep('adding test_hooks.lua', "stderr")) + +check(cmd(mtn("status")), 0, true) +check(not qgrep("file0", "stdout")) +check(not qgrep("file1", "stdout")) +check(not qgrep("file2", "stdout")) +check(qgrep("file3", "stdout")) +--check(not qgrep("file4", "stdout")) +check(qgrep("file5", "stdout")) +--check(not qgrep("file6", "stdout")) + +commit() + +check(cmd(mtn("status")), 0, true) +check(not qgrep("file0", "stdout")) +check(not qgrep("file1", "stdout")) +check(not qgrep("file2", "stdout")) +check(not qgrep("file3", "stdout")) +--check(not qgrep("file4", "stdout")) +check(not qgrep("file5", "stdout")) +--check(not qgrep("file6", "stdout")) ============================================================ --- tests/calculation_of_incorrect_unidiffs/__driver__.lua 3e252f3773737b1c3732d31bd555019b1ef78d53 +++ tests/calculation_of_incorrect_unidiffs/__driver__.lua 3e252f3773737b1c3732d31bd555019b1ef78d53 @@ -0,0 +1,18 @@ + +mtn_setup() + +-- I don't get it. This seems to work, but WTF is that qgrep looking for? + +-- decode first file and commit to db +getfile("firstfile", "testfile") +addfile("testfile") +commit() +os.rename("testfile", "firstfile") + +-- calculate diff to second file using monotone +getfile("secondfile", "testfile") +check(cmd(mtn("diff")), 0, true) +os.rename("stdout", "monodiff") + +-- look for a meaningless change +check(not qgrep("^-$", "monodiff")) ============================================================ --- tests/calculation_of_incorrect_unidiffs/firstfile 2fdc5405a533dfc81e28e02c482ca3d783225d51 +++ tests/calculation_of_incorrect_unidiffs/firstfile 2fdc5405a533dfc81e28e02c482ca3d783225d51 @@ -0,0 +1,1668 @@ +// copyright (C) 2002, 2003 graydon hoare +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "commands.hh" +#include "constants.hh" + +#include "app_state.hh" +#include "diff_patch.hh" +#include "file_io.hh" +#include "keys.hh" +#include "manifest.hh" +#include "network.hh" +#include "packet.hh" +#include "patch_set.hh" +#include "rcs_import.hh" +#include "sanity.hh" +#include "cert.hh" +#include "transforms.hh" +#include "update.hh" +#include "vocab.hh" +#include "work.hh" + +// +// this file defines the task-oriented "top level" commands which can be +// issued as part of a monotone command line. the command line can only +// have one such command on it, followed by a vector of strings which are its +// arguments. all --options will be processed by the main program *before* +// calling a command +// +// we might expose this blunt command interface to scripting someday. but +// not today. + +namespace commands +{ + struct command; + bool operator<(command const & self, command const & other); +}; + +namespace std +{ + template <> + struct std::greater + { + bool operator()(commands::command const * a, commands::command const * b) + { + return *a < *b; + } + }; +}; + +namespace commands +{ +using namespace std; + +struct command; + +static map cmds; + +struct command +{ + string name; + string cmdgroup; + string params; + string desc; + command(string const & n, + string const & g, + string const & p, + string const & d) : name(n), cmdgroup(g), params(p), desc(d) + { cmds[n] = this; } + virtual ~command() {} + virtual void exec(app_state & app, vector const & args) = 0; +}; + +bool operator<(command const & self, command const & other) +{ + return ((self.cmdgroup < other.cmdgroup) + || ((self.cmdgroup == other.cmdgroup) && (self.name < other.name))); +} + + +void explain_usage(string const & cmd, ostream & out) +{ + map::const_iterator i; + i = cmds.find(cmd); + if (i != cmds.end()) + { + out << " " << i->second->name << " " << i->second->params << endl + << " " << i->second->desc << endl << endl; + return; + } + + vector sorted; + out << "commands:" << endl; + for (i = cmds.begin(); i != cmds.end(); ++i) + { + sorted.push_back(i->second); + } + + sort(sorted.begin(), sorted.end(), std::greater()); + + string curr_group; + size_t col = 0; + size_t col2 = 0; + for (size_t i = 0; i < sorted.size(); ++i) + { + col2 = col2 > sorted[i]->cmdgroup.size() ? col2 : sorted[i]->cmdgroup.size(); + } + + for (size_t i = 0; i < sorted.size(); ++i) + { + if (sorted[i]->cmdgroup != curr_group) + { + curr_group = sorted[i]->cmdgroup; + out << endl; + out << " " << sorted[i]->cmdgroup; + col = sorted[i]->cmdgroup.size() + 2; + while (col++ < (col2 + 3)) + out << ' '; + } + out << " " << sorted[i]->name; + col += sorted[i]->name.size() + 1; + if (col >= 70) + { + out << endl; + col = 0; + while (col++ < (col2 + 3)) + out << ' '; + } + } + out << endl << endl; +} + +void process(app_state & app, string const & cmd, vector const & args) +{ + if (cmds.find(cmd) != cmds.end()) + { + L("executing %s command\n", cmd.c_str()); + cmds[cmd]->exec(app, args); + } + else + { + throw usage(cmd); + } +} + +#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) \ + +#define ALIAS(C, realcommand, group, params, desc) \ +CMD(C, group, params, desc) \ +{ \ + process(app, string(#realcommand), args); \ +} + + +static void ensure_bookdir() +{ + mkdir_p(local_path(book_keeping_dir)); +} + +static void get_manifest_path(local_path & m_path) +{ + m_path = (fs::path(book_keeping_dir) / fs::path(manifest_file_name)).string(); + L("manifest path is %s\n", m_path().c_str()); +} + +static void get_work_path(local_path & w_path) +{ + w_path = (fs::path(book_keeping_dir) / fs::path(work_file_name)).string(); + L("work path is %s\n", w_path().c_str()); +} + +static void get_manifest_map(manifest_map & m) +{ + ensure_bookdir(); + local_path m_path; + base64< gzip > m_data; + get_manifest_path(m_path); + if (file_exists(m_path)) + { + L("loading manifest file %s\n", m_path().c_str()); + read_data(m_path, m_data); + read_manifest_map(manifest_data(m_data), m); + L("read %d manifest entries\n", m.size()); + } + else + { + L("no manifest file %s\n", m_path().c_str()); + } +} + +static void put_manifest_map(manifest_map const & m) +{ + ensure_bookdir(); + local_path m_path; + manifest_data m_data; + get_manifest_path(m_path); + L("writing manifest file %s\n", m_path().c_str()); + write_manifest_map(m, m_data); + write_data(m_path, m_data.inner()); + L("wrote %d manifest entries\n", m.size()); +} + +static void get_work_set(work_set & w) +{ + ensure_bookdir(); + local_path w_path; + get_work_path(w_path); + if (file_exists(w_path)) + { + L("checking for un-committed work file %s\n", + w_path().c_str()); + data w_data; + read_data(w_path, w_data); + read_work_set(w_data, w); + L("read %d dels, %d adds from %s\n", + w.dels.size(), w.adds.size(), w_path().c_str()); + } + else + { + L("no un-committed work file %s\n", w_path().c_str()); + } +} + +static void put_work_set(work_set & w) +{ + local_path w_path; + get_work_path(w_path); + + if (w.adds.size() > 0 + || w.dels.size() > 0) + { + ensure_bookdir(); + data w_data; + write_work_set(w_data, w); + write_data(w_path, w_data); + } + else + { + delete_file(w_path); + } +} + +static void calculate_new_manifest_map(manifest_map const & m_old, + manifest_map & m_new) +{ + path_set paths; + work_set work; + extract_path_set(m_old, paths); + get_work_set(work); + if (work.dels.size() > 0) + L("removing %d dead files from manifest\n", + work.dels.size()); + if (work.adds.size() > 0) + L("adding %d files to manifest\n", work.adds.size()); + apply_work_set(work, paths); + build_manifest_map(paths, m_new); +} + +static string get_stdin() +{ + char buf[bufsz]; + string tmp; + while(cin) + { + cin.read(buf, bufsz); + tmp.append(buf, cin.gcount()); + } + return tmp; +} + +static void get_log_message(patch_set const & ps, + app_state & app, + string & log_message) +{ + string commentary; + string summary; + stringstream ss; + patch_set_to_text_summary(ps, ss); + summary = ss.str(); + commentary += "----------------------------------------------------------------------\n"; + commentary += "Enter Log. Lines beginning with `MT:' are removed automatically\n"; + commentary += "\n"; + commentary += "Summary of changes:\n"; + commentary += "\n"; + commentary += summary; + commentary += "----------------------------------------------------------------------\n"; + N(app.lua.hook_edit_comment(commentary, log_message), + "edit of log message failed"); +} + + +// the goal here is to look back through the ancestry of the provided +// child, checking to see the least ancestor it has which we received from +// the given network url/group pair. +// +// we use the ancestor as the source manifest when building a patchset to +// send to that url/group. + +static bool find_ancestor_on_netserver (manifest_id const & child, + url const & u, + group const & g, + manifest_id & anc, + app_state & app) +{ + set frontier; + cert_name tn(ancestor_cert_name); + frontier.insert(child); + + while (!frontier.empty()) + { + set next_frontier; + for (set::const_iterator i = frontier.begin(); + i != frontier.end(); ++i) + { + vector< manifest > tmp; + app.db.get_manifest_certs(*i, tn, tmp); + + // we go through this vector backwards because we would prefer to + // hit more recently-queued ancestors (such as intermediate nodes + // in a multi-node merge) rather than older ancestors. but of + // course, any ancestor will do. + + for (vector< manifest >::reverse_iterator j = tmp.rbegin(); + j != tmp.rend(); ++j) + { + cert_value tv; + decode_base64(j->inner().value, tv); + manifest_id anc_id (tv()); + + L("looking for parent %s of %s on server\n", + i->inner()().c_str(), + anc_id.inner()().c_str()); + + if (app.db.manifest_exists_on_netserver (u, g, anc_id)) + { + L("found parent %s on server\n", anc_id.inner()().c_str()); + anc = anc_id; + return true; + } + else + next_frontier.insert(anc_id); + } + } + + frontier = next_frontier; + } + + return false; +} + + +static void queue_edge_for_target_ancestor (pair const & targ, + manifest_id const & child_id, + manifest_map const & child_map, + app_state & app) +{ + // now here is an interesting thing: we *might* be sending data to a + // depot, or someone with indeterminate pre-existing state (say the first + // time we post to netnews), therefore we cannot just "send the edge" we + // just constructed in a merge or commit, we need to send an edge from a + // parent which we know to be present in the depot (or else an edge from + // the empty map -- full contents of all files). what is sent therefore + // changes on a depot-by-depot basis. this function calculates the + // appropriate thing to send. + // + // nb: this has no direct relation to what we store in our own + // database. we always store the edge from our parent, and we always know + // when we have a parent. + + vector< pair > one_target; + one_target.push_back(targ); + queueing_packet_writer qpw(app, one_target); + + manifest_data targ_ancestor_data; + manifest_map targ_ancestor_map; + manifest_id targ_ancestor_id; + + if (find_ancestor_on_netserver (child_id, + targ.first, + targ.second, + targ_ancestor_id, + app)) + { + app.db.get_manifest_version(targ_ancestor_id, targ_ancestor_data); + read_manifest_map(targ_ancestor_data, targ_ancestor_map); + } + + patch_set ps; + manifests_to_patch_set(targ_ancestor_map, child_map, app, ps); + patch_set_to_packets(ps, app, qpw); + + // now that we've queued the data, we can note this new child + // node as existing (well .. soon-to-exist) on the server + app.db.note_manifest_on_netserver (targ.first, targ.second, child_id); + +} + + +// this helper tries to produce merge <- mergeN(left,right), possibly +// merge3 if it can find an ancestor, otherwise merge2. it also queues the +// appropriate edges from known ancestors to the new merge node, to be +// transmitted to each of the targets provided. + +static void try_one_merge(manifest_id const & left, + manifest_id const & right, + manifest_id & merged, + app_state & app, + vector< pair > const & targets) +{ + manifest_data left_data, right_data, ancestor_data, merged_data; + manifest_map left_map, right_map, ancestor_map, merged_map; + manifest_id ancestor; + + app.db.get_manifest_version(left, left_data); + app.db.get_manifest_version(right, right_data); + read_manifest_map(left_data, left_map); + read_manifest_map(right_data, right_map); + + simple_merge_provider merger(app); + + if(find_common_ancestor(left, right, ancestor, app)) + { + P("common ancestor %s found, trying merge3\n", ancestor.inner()().c_str()); + app.db.get_manifest_version(ancestor, ancestor_data); + read_manifest_map(ancestor_data, ancestor_map); + N(merge3(ancestor_map, left_map, right_map, + app, merger, merged_map), + (string("failed to merge manifests ") + + left.inner()() + " and " + right.inner()())); + } + else + { + P("no common ancestor found, trying merge2\n"); + N(merge2(left_map, right_map, app, merger, merged_map), + (string("failed to merge manifests ") + + left.inner()() + " and " + right.inner()())); + } + + write_manifest_map(merged_map, merged_data); + calculate_manifest_map_ident(merged_map, merged); + + base64< gzip > left_edge; + diff(left_data.inner(), merged_data.inner(), left_edge); + + // FIXME: we do *not* manufacture or store the second edge to + // the merged version, since doing so violates the + // assumptions of the db, and the 'right' version already + // exists in its entirety, anyways. this is a subtle issue + // though and I'm not sure I'm making the right + // decision. revisit. if you do not see that it is a subtle + // issue I suggest you are not thinking about it long enough. + // + // base64< gzip > right_edge; + // diff(right_data.inner(), merged_data.inner(), right_edge); + // app.db.put_manifest_version(right, merged, right_edge); + + + // we do of course record the left edge, and ancestry relationship to + // both predecessors. + + { + packet_db_writer dbw(app); + + dbw.consume_manifest_delta(left, merged, left_edge); + cert_manifest_ancestor(left, merged, app, dbw); + cert_manifest_ancestor(right, merged, app, dbw); + cert_manifest_date_now(merged, app, dbw); + cert_manifest_author_default(merged, app, dbw); + + // make sure the appropriate edges get queued for the network. + for (vector< pair >::const_iterator targ = targets.begin(); + targ != targets.end(); ++targ) + { + queue_edge_for_target_ancestor (*targ, merged, merged_map, app); + } + + queueing_packet_writer qpw(app, targets); + cert_manifest_ancestor(left, merged, app, qpw); + cert_manifest_ancestor(right, merged, app, qpw); + cert_manifest_date_now(merged, app, qpw); + cert_manifest_author_default(merged, app, qpw); + } + +} + + +// actual commands follow + +CMD(lscerts, "key and cert", "(file|manifest) ", + "list certs associated with manifest or file") +{ + if (args.size() != 2) + throw usage(name); + + vector certs; + + transaction_guard guard(app.db); + + if (args[0] == "manifest") + { + manifest_id ident(args[1]); + vector< manifest > ts; + app.db.get_manifest_certs(ident, ts); + for (size_t i = 0; i < ts.size(); ++i) + certs.push_back(ts[i].inner()); + } + else if (args[0] == "file") + { + file_id ident(args[1]); + vector< file > ts; + app.db.get_file_certs(ident, ts); + for (size_t i = 0; i < ts.size(); ++i) + certs.push_back(ts[i].inner()); + } + else + throw usage(name); + + for (size_t i = 0; i < certs.size(); ++i) + { + bool ok = check_cert(app, certs[i]); + cert_value tv; + decode_base64(certs[i].value, tv); + string washed; + if (guess_binary(tv())) + { + washed = ""; + } + else + { + washed = tv(); + } + string head = string(ok ? "ok sig from " : "bad sig from ") + + "[" + certs[i].key() + "] : " + + "[" + certs[i].name() + "] = ["; + string pad(head.size(), ' '); + vector lines; + split_into_lines(washed, lines); + I(lines.size() > 0); + cout << head << lines[0] ; + for (size_t i = 1; i < lines.size(); ++i) + cout << endl << pad << lines[i]; + cout << "]" << endl; + } + guard.commit(); +} + +CMD(lskeys, "key and cert", "[partial-id]", "list keys") +{ + vector pubkeys; + vector privkeys; + + transaction_guard guard(app.db); + + if (args.size() == 0) + app.db.get_key_ids("", pubkeys, privkeys); + else if (args.size() == 1) + app.db.get_key_ids(args[0], pubkeys, privkeys); + else + throw usage(name); + + if (pubkeys.size() > 0) + { + cout << endl << "[public keys]" << endl; + for (size_t i = 0; i < pubkeys.size(); ++i) + cout << pubkeys[i]() << endl; + cout << endl; + } + + if (privkeys.size() > 0) + { + cout << endl << "[private keys]" << endl; + for (size_t i = 0; i < privkeys.size(); ++i) + cout << privkeys[i]() << endl; + cout << endl; + } + + guard.commit(); +} + +CMD(genkey, "key and cert", "", "generate an RSA key-pair") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + rsa_keypair_id ident(args[0]); + + N(! app.db.key_exists(ident), + (string("key '") + ident() + "' already exists in database")); + + base64 pub; + base64< arc4 > priv; + P("generating key-pair '%s'\n", ident().c_str()); + generate_key_pair(app.lua, ident, pub, priv); + P("storing key-pair '%s' in database\n", ident().c_str()); + app.db.put_key_pair(ident, pub, priv); + + guard.commit(); +} + +CMD(cert, "key and cert", "(file|manifest) [certval]", + "create a cert for a file or manifest") +{ + if ((args.size() != 4) && (args.size() != 3)) + throw usage(name); + + transaction_guard guard(app.db); + + hexenc ident(args[1]); + cert_name name(args[2]); + + rsa_keypair_id key; + if (app.signing_key() != "") + key = app.signing_key; + else + N(guess_default_key(key, app), + "no unique private key found, and no key specified"); + + cert_value val; + if (args.size() == 4) + val = cert_value(args[3]); + else + val = cert_value(get_stdin()); + + base64 val_encoded; + encode_base64(val, val_encoded); + + cert t(ident, name, val_encoded, key); + + // nb: we want to throw usage on mis-use *before* asking for a + // passphrase. + + if (args[0] == "file") + { + calculate_cert(app, t); + app.db.put_file_cert(file(t)); + } + else if (args[0] == "manifest") + { + calculate_cert(app, t); + app.db.put_manifest_cert(manifest(t)); + } + else + throw usage(this->name); + + guard.commit(); +} + + +CMD(tag, "certificate", " ", + "put a symbolic tag cert on a manifest version") +{ + if (args.size() != 2) + throw usage(name); + manifest_id m(args[0]); + packet_db_writer dbw(app); + cert_manifest_tag(m, args[1], app, dbw); +} + +CMD(approve, "certificate", "(file|manifest) ", + "approve of a manifest or file version") +{ + if (args.size() != 2) + throw usage(name); + if (args[0] == "manifest") + { + manifest_id m(args[1]); + packet_db_writer dbw(app); + cert_manifest_approval(m, true, app, dbw); + } + else if (args[0] == "file") + { + packet_db_writer dbw(app); + file_id f(args[1]); + cert_file_approval(f, true, app, dbw); + } + else + throw usage(name); +} + +CMD(disapprove, "certificate", "(file|manifest) ", + "disapprove of a manifest or file version") +{ + if (args.size() != 2) + throw usage(name); + if (args[0] == "manifest") + { + manifest_id m(args[1]); + packet_db_writer dbw(app); + cert_manifest_approval(m, false, app, dbw); + } + else if (args[0] == "file") + { + file_id f(args[1]); + packet_db_writer dbw(app); + cert_file_approval(f, false, app, dbw); + } + else + throw usage(name); +} + + +CMD(comment, "certificate", "(file|manifest) [comment]", + "comment on a file or manifest version") +{ + if (args.size() != 2 && args.size() != 3) + throw usage(name); + + string comment; + if (args.size() == 3) + comment = args[2]; + else + N(app.lua.hook_edit_comment("", comment), "edit comment failed"); + + N(comment.find_first_not_of(" \r\t\n") == string::npos, "empty comment"); + + if (args[0] == "file") + { + packet_db_writer dbw(app); + cert_file_comment(file_id(args[1]), comment, app, dbw); + } + else if (args[0] == "manifest") + { + packet_db_writer dbw(app); + cert_manifest_comment(manifest_id(args[1]), comment, app, dbw); + } + else + throw usage(name); +} + + + +CMD(add, "working copy", " [...]", "add files to working copy") +{ + if (args.size() < 1) + throw usage(name); + + transaction_guard guard(app.db); + + manifest_map man; + work_set work; + get_manifest_map(man); + get_work_set(work); + bool rewrite_work = false; + + for (vector::const_iterator i = args.begin(); i != args.end(); ++i) + build_addition(file_path(*i), app, work, man, rewrite_work); + + guard.commit(); + + // small race here + if (rewrite_work) + put_work_set(work); +} + +CMD(drop, "working copy", " [...]", "drop files from working copy") +{ + if (args.size() < 1) + throw usage(name); + + manifest_map man; + work_set work; + get_manifest_map(man); + get_work_set(work); + bool rewrite_work = false; + + transaction_guard guard(app.db); + + for (vector::const_iterator i = args.begin(); i != args.end(); ++i) + build_deletion(file_path(*i), app, work, man, rewrite_work); + + guard.commit(); + + // small race here + if (rewrite_work) + put_work_set(work); +} + +CMD(commit, "working copy", "[log message]", "commit working copy to database") +{ + string log_message(""); + manifest_map m_old, m_new; + patch_set ps; + + get_manifest_map(m_old); + calculate_new_manifest_map(m_old, m_new); + manifest_id old_id, new_id; + calculate_manifest_map_ident(m_old, old_id); + calculate_manifest_map_ident(m_new, new_id); + + if (args.size() != 0 && args.size() != 1) + throw usage(name); + + cert_value branchname; + if (app.branch_name != "") + { + branchname = app.branch_name; + } + else + { + vector< manifest > certs; + cert_name branch(branch_cert_name); + app.db.get_manifest_certs(old_id, branch, certs); + + N(certs.size() != 0, + string("no branch certs found for old manifest ") + + old_id.inner()() + ", please provide a branch name"); + + N(certs.size() == 1, + string("multiple branch certs found for old manifest ") + + old_id.inner()() + ", please provide a branch name"); + + decode_base64(certs[0].inner().value, branchname); + } + + L("committing %s to branch %s\n", + new_id.inner()().c_str(), branchname().c_str()); + app.branch_name = branchname(); + + manifests_to_patch_set(m_old, m_new, app, ps); + + // get log message + if (args.size() == 1) + log_message = args[0]; + else + get_log_message(ps, app, log_message); + + N(log_message.find_first_not_of(" \r\t\n") != string::npos, + "empty log message"); + + { + transaction_guard guard(app.db); + + // process manifest delta or new manifest + if (app.db.manifest_version_exists(ps.m_new)) + { + L("skipping manifest %s, already in database\n", ps.m_new.inner()().c_str()); + } + else + { + if (app.db.manifest_version_exists(ps.m_old)) + { + L("inserting manifest delta %s -> %s\n", + ps.m_old.inner()().c_str(), ps.m_new.inner()().c_str()); + manifest_data m_old_data, m_new_data; + app.db.get_manifest_version(ps.m_old, m_old_data); + write_manifest_map(m_new, m_new_data); + base64< gzip > del; + diff(m_old_data.inner(), m_new_data.inner(), del); + app.db.put_manifest_version(ps.m_old, ps.m_new, manifest_delta(del)); + } + else + { + L("inserting full manifest %s\n", + ps.m_new.inner()().c_str()); + manifest_data m_new_data; + write_manifest_map(m_new, m_new_data); + app.db.put_manifest(ps.m_new, m_new_data); + } + } + + // process file deltas + for (set::const_iterator i = ps.f_deltas.begin(); + i != ps.f_deltas.end(); ++i) + { + if (app.db.file_version_exists(i->id_new)) + { + L("skipping file delta %s, already in database\n", i->id_new.inner()().c_str()); + } + else + { + if (app.db.file_version_exists(i->id_old)) + { + L("inserting delta %s -> %s\n", + i->id_old.inner()().c_str(), i->id_new.inner()().c_str()); + file_data old_data; + base64< gzip > new_data; + app.db.get_file_version(i->id_old, old_data); + read_data(i->path, new_data); + base64< gzip > del; + diff(old_data.inner(), new_data, del); + app.db.put_file_version(i->id_old, i->id_new, file_delta(del)); + } + else + { + L("inserting full version %s\n", i->id_old.inner()().c_str()); + base64< gzip > new_data; + read_data(i->path, new_data); + // sanity check + hexenc tid; + calculate_ident(new_data, tid); + I(tid == i->id_new.inner()); + app.db.put_file(i->id_new, file_data(new_data)); + } + } + } + + // process file adds + for (set::const_iterator i = ps.f_adds.begin(); + i != ps.f_adds.end(); ++i) + { + if (app.db.file_version_exists(i->ident)) + { + L("skipping file %s %s, already in database\n", + i->path().c_str(), i->ident.inner()().c_str()); + } + else + { + // it's a new file + L("inserting new file %s %s\n", + i->path().c_str(), i->ident.inner()().c_str()); + base64< gzip > new_data; + read_data(i->path, new_data); + app.db.put_file(i->ident, new_data); + } + } + + packet_db_writer dbw(app); + + if (! m_old.empty()) + cert_manifest_ancestor(ps.m_old, ps.m_new, app, dbw); + + cert_manifest_in_branch(ps.m_new, branchname, app, dbw); + cert_manifest_date_now(ps.m_new, app, dbw); + cert_manifest_author_default(ps.m_new, app, dbw); + cert_manifest_changelog(ps.m_new, log_message, app, dbw); + + // commit done, now queue diff for sending + + if (app.db.manifest_version_exists(ps.m_new)) + { + vector< pair > targets; + app.lua.hook_get_post_targets(branchname, targets); + + // make sure the appropriate edges get queued for the network. + for (vector< pair >::const_iterator targ = targets.begin(); + targ != targets.end(); ++targ) + { + queue_edge_for_target_ancestor (*targ, ps.m_new, m_new, app); + } + + // throw in all available certs for good measure + queueing_packet_writer qpw(app, targets); + vector< manifest > certs; + app.db.get_manifest_certs(ps.m_new, certs); + for(vector< manifest >::const_iterator i = certs.begin(); + i != certs.end(); ++i) + qpw.consume_manifest_cert(*i); + } + + guard.commit(); + } + // small race condition here... + local_path w_path; + get_work_path(w_path); + delete_file(w_path); + put_manifest_map(m_new); + P("committed %s\n", ps.m_new.inner()().c_str()); +} + +CMD(update, "working copy", "[sort keys...]", "update working copy, relative to sorting keys") +{ + + manifest_data m_chosen_data; + manifest_map m_old, m_working, m_chosen, m_new; + manifest_id m_old_id, m_chosen_id; + + transaction_guard guard(app.db); + + get_manifest_map(m_old); + calculate_manifest_map_ident(m_old, m_old_id); + calculate_new_manifest_map(m_old, m_working); + + pick_update_target(m_old_id, args, app, m_chosen_id); + P("selected update target %s\n", + m_chosen_id.inner()().c_str()); + app.db.get_manifest_version(m_chosen_id, m_chosen_data); + read_manifest_map(m_chosen_data, m_chosen); + + update_merge_provider merger(app); + N(merge3(m_old, m_chosen, m_working, app, merger, m_new), + string("manifest merge failed, no update performed")); + + P("calculating patchset for update\n"); + patch_set ps; + manifests_to_patch_set(m_working, m_new, app, ps); + + L("applying %d deletions to files in tree\n", ps.f_dels.size()); + for (set::const_iterator i = ps.f_dels.begin(); + i != ps.f_dels.end(); ++i) + { + L("deleting %s\n", (*i)().c_str()); + delete_file(*i); + } + + L("applying %d moves to files in tree\n", ps.f_moves.size()); + for (set::const_iterator i = ps.f_moves.begin(); + i != ps.f_moves.end(); ++i) + { + L("moving %s -> %s\n", i->path_old().c_str(), i->path_new().c_str()); + move_file(i->path_old, i->path_new); + } + + L("applying %d additions to tree\n", ps.f_adds.size()); + for (set::const_iterator i = ps.f_adds.begin(); + i != ps.f_adds.end(); ++i) + { + L("adding %s as %s\n", i->ident.inner()().c_str(), i->path().c_str()); + file_data tmp; + if (app.db.file_version_exists(i->ident)) + app.db.get_file_version(i->ident, tmp); + else if (merger.temporary_store.find(i->ident) != merger.temporary_store.end()) + tmp = merger.temporary_store[i->ident]; + else + I(false); // trip assert. this should be impossible. + write_data(i->path, tmp.inner()); + } + + L("applying %d deltas to tree\n", ps.f_deltas.size()); + for (set::const_iterator i = ps.f_deltas.begin(); + i != ps.f_deltas.end(); ++i) + { + P("updating file %s: %s -> %s\n", + i->path().c_str(), + i->id_old.inner()().c_str(), + i->id_new.inner()().c_str()); + + // sanity check + { + base64< gzip > dtmp; + hexenc dtmp_id; + read_data(i->path, dtmp); + calculate_ident(dtmp, dtmp_id); + I(dtmp_id == i->id_old.inner()); + } + + // ok, replace with new version + { + file_data tmp; + if (app.db.file_version_exists(i->id_new)) + app.db.get_file_version(i->id_new, tmp); + else if (merger.temporary_store.find(i->id_new) != merger.temporary_store.end()) + tmp = merger.temporary_store[i->id_new]; + else + I(false); // trip assert. this should be impossible. + write_data(i->path, tmp.inner()); + } + } + + L("update successful\n"); + guard.commit(); + + // small race condition here... + // nb: we write out m_chosen, not m_new, because the manifest-on-disk + // is the basis of the working copy, not the working copy itself. + put_manifest_map(m_chosen); + P("updated to base version %s\n", m_chosen_id.inner()().c_str()); +} + + + +CMD(cat, "tree", "(file|manifest) ", "write file or manifest from database to stdout") +{ + if (args.size() != 2) + throw usage(name); + + transaction_guard guard(app.db); + + if (args[0] == "file") + { + file_data dat; + file_id ident(args[1]); + + N(app.db.file_version_exists(ident), + (string("no file version ") + ident.inner()() + " found in database")); + + L("dumping file %s\n", ident.inner()().c_str()); + app.db.get_file_version(ident, dat); + data unpacked; + unpack(dat.inner(), unpacked); + cout.write(unpacked().data(), unpacked().size()); + + } + else if (args[0] == "manifest") + { + manifest_data dat; + manifest_id ident(args[1]); + + N(app.db.manifest_version_exists(ident), + (string("no file version ") + ident.inner()() + " found in database")); + + L("dumping manifest %s\n", ident.inner()().c_str()); + app.db.get_manifest_version(ident, dat); + data unpacked; + unpack(dat.inner(), unpacked); + cout.write(unpacked().data(), unpacked().size()); + } + else + throw usage(name); + + guard.commit(); +} + + +CMD(checkout, "tree", "", "check out tree state from database") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + + file_data data; + manifest_id ident(args[0]); + manifest_map m; + + N(app.db.manifest_version_exists(ident), + (string("no manifest version ") + ident.inner()() + " found in database")); + + L("exporting manifest %s\n", ident.inner()().c_str()); + manifest_data m_data; + app.db.get_manifest_version(ident, m_data); + read_manifest_map(m_data, m); + put_manifest_map(m); + + for (manifest_map::const_iterator i = m.begin(); i != m.end(); ++i) + { + vector args; + path_id_pair pip(*i); + + N(app.db.file_version_exists(pip.ident()), + (string("no file version ") + + pip.ident().inner()() + + " found in database for " + + pip.path()().c_str())); + + file_data dat; + L("writing file %s to %s\n", + pip.ident().inner()().c_str(), + pip.path()().c_str()); + app.db.get_file_version(pip.ident(), dat); + write_data(pip.path(), dat.inner()); + } + + guard.commit(); +} + +ALIAS(co, checkout, "tree", "", + "check out tree state from database; alias for checkout") + +CMD(heads, "tree", "", "show unmerged heads of branch") +{ + vector heads; + if (args.size() != 0) + throw usage(name); + + if (app.branch_name == "") + { + cout << "please specify a branch, with --branch=" << endl; + return; + } + + get_branch_heads(app.branch_name, app, heads); + + if (heads.size() == 0) + cout << "branch '" << app.branch_name << "' is empty" << endl; + else if (heads.size() == 1) + cout << "branch '" << app.branch_name << "' is currently merged:" << endl; + else + cout << "branch '" << app.branch_name << "' is currently unmerged:" << endl; + + for (vector::const_iterator i = heads.begin(); + i != heads.end(); ++i) + { + cout << i->inner()() << endl; + } +} + + +CMD(merge, "tree", "", "merge unmerged heads of branch") +{ + + vector heads; + + if (args.size() != 0) + throw usage(name); + + if (app.branch_name == "") + { + cout << "please specify a branch, with --branch=" << endl; + return; + } + + get_branch_heads(app.branch_name, app, heads); + + if (heads.size() == 0) + { + cout << "branch " << args[0] << "is empty" << endl; + return; + } + else if (heads.size() == 1) + { + cout << "branch " << args[0] << "is merged" << endl; + return; + } + else + { + vector< pair > targets; + app.lua.hook_get_post_targets(app.branch_name, targets); + + manifest_id left = heads[0]; + manifest_id ancestor; + for (size_t i = 1; i < heads.size(); ++i) + { + manifest_id right = heads[i]; + P("merging with manifest %d / %d: %s <-> %s\n", + i, heads.size(), + left.inner()().c_str(), right.inner()().c_str()); + + manifest_id merged; + transaction_guard guard(app.db); + try_one_merge (left, right, merged, app, targets); + + // merged 1 edge; now we commit this, update merge source and + // try next one + + packet_db_writer dbw(app); + queueing_packet_writer qpw(app, targets); + cert_manifest_in_branch(merged, app.branch_name, app, dbw); + cert_manifest_in_branch(merged, app.branch_name, app, qpw); + + string log = "merge of " + left.inner()() + " and " + right.inner()(); + cert_manifest_changelog(merged, log, app, dbw); + cert_manifest_changelog(merged, log, app, qpw); + + guard.commit(); + P("[source] %s\n[source] %s\n[merged] %s\n", + left.inner()().c_str(), + right.inner()().c_str(), + merged.inner()().c_str()); + left = merged; + } + } +} + + +CMD(propagate, "tree", " ", + "merge from one branch to another asymmetrically") +{ + /* + + this is a special merge operator, but very useful for people maintaining + "slightly disparate but related" trees. it does a one-way merge; less + powerful than putting things in the same branch and also more flexible. + + 1. check to see if src and dst branches are merged, if not abort, if so + call heads N1 and N2 respectively. + + 2. (FIXME: not yet present) run the hook propagate ("src-branch", + "dst-branch", N1, N2) which gives the user a chance to massage N1 into + a state which is likely to "merge nicely" with N2, eg. edit pathnames, + omit optional files of no interest. + + 3. do a normal 2 or 3-way merge on N1 and N2, depending on the + existence of common ancestors. + + 4. save the results as the delta (N2,M), the ancestry edges (N1,M) + and (N2,M), and the cert (N2,dst). + + 5. queue the resulting packets to send to the url for dst-branch, not + src-branch. + + */ + + vector src_heads, dst_heads; + + if (args.size() != 2) + throw usage(name); + + get_branch_heads(args[0], app, src_heads); + get_branch_heads(args[1], app, dst_heads); + + if (src_heads.size() == 0) + { + cout << "branch " << args[0] << "is empty" << endl; + return; + } + else if (src_heads.size() != 1) + { + cout << "branch " << args[0] << "is not merged" << endl; + return; + } + else if (dst_heads.size() == 0) + { + cout << "branch " << args[1] << "is empty" << endl; + return; + } + else if (dst_heads.size() != 1) + { + cout << "branch " << args[1] << "is not merged" << endl; + return; + } + else + { + vector< pair > targets; + app.lua.hook_get_post_targets(args[1], targets); + + manifest_id merged; + transaction_guard guard(app.db); + try_one_merge (src_heads[0], dst_heads[0], merged, app, targets); + + queueing_packet_writer qpw(app, targets); + cert_manifest_in_branch(merged, app.branch_name, app, qpw); + cert_manifest_changelog(merged, + "propagate of " + + src_heads[0].inner()() + + " and " + + dst_heads[0].inner()() + + "\n" + + "from branch " + + args[0] + " to " + args[1] + "\n", + app, qpw); + guard.commit(); + } +} + + + +CMD(diff, "informative", "", "show current diffs on stdout") +{ + manifest_map m_old, m_new; + patch_set ps; + + transaction_guard guard(app.db); + + get_manifest_map(m_old); + calculate_new_manifest_map(m_old, m_new); + manifests_to_patch_set(m_old, m_new, app, ps); + + for (set::const_iterator i = ps.f_deltas.begin(); + i != ps.f_deltas.end(); ++i) + { + file_data f_old; + gzip decoded_old; + data decompressed_old, decompressed_new; + vector old_lines, new_lines; + + app.db.get_file_version(i->id_old, f_old); + decode_base64(f_old.inner(), decoded_old); + decode_gzip(decoded_old, decompressed_old); + + read_data(i->path, decompressed_new); + + split_into_lines(decompressed_old(), old_lines); + split_into_lines(decompressed_new(), new_lines); + + unidiff(i->path(), i->path(), old_lines, new_lines, cout); + } + guard.commit(); +} + +CMD(status, "informative", "", "show status of working copy") +{ + manifest_map m_old, m_new; + patch_set ps; + + transaction_guard guard(app.db); + get_manifest_map(m_old); + calculate_new_manifest_map(m_old, m_new); + manifests_to_patch_set(m_old, m_new, app, ps); + patch_set_to_text_summary(ps, cout); + guard.commit(); +} + + +CMD(mdelta, "packet i/o", " ", "write manifest delta packet to stdout") +{ + if (args.size() != 2) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + manifest_id m_old_id, m_new_id; + manifest_data m_old_data, m_new_data; + manifest_map m_old, m_new; + patch_set ps; + m_old_id = hexenc(args[0]); + m_new_id = hexenc(args[1]); + app.db.get_manifest_version(m_old_id, m_old_data); + app.db.get_manifest_version(m_new_id, m_new_data); + read_manifest_map(m_old_data, m_old); + read_manifest_map(m_new_data, m_new); + manifests_to_patch_set(m_old, m_new, app, ps); + patch_set_to_packets(ps, app, pw); + guard.commit(); +} + +CMD(fdelta, "packet i/o", " ", "write file delta packet to stdout") +{ + if (args.size() != 2) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + file_id f_old_id, f_new_id; + file_data f_old_data, f_new_data; + f_old_id = hexenc(args[0]); + f_new_id = hexenc(args[1]); + app.db.get_file_version(f_old_id, f_old_data); + app.db.get_file_version(f_new_id, f_new_data); + base64< gzip > del; + diff(f_old_data.inner(), f_new_data.inner(), del); + pw.consume_file_delta(f_old_id, f_new_id, file_delta(del)); + guard.commit(); +} + +CMD(mdata, "packet i/o", "", "write manifest data packet to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + manifest_id m_id; + manifest_data m_data; + m_id = hexenc(args[0]); + app.db.get_manifest_version(m_id, m_data); + pw.consume_manifest_data(m_id, m_data); + guard.commit(); +} + + +CMD(fdata, "packet i/o", "", "write file data packet to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + file_id f_id; + file_data f_data; + f_id = hexenc(args[0]); + app.db.get_file_version(f_id, f_data); + pw.consume_file_data(f_id, f_data); + guard.commit(); +} + +CMD(mcerts, "packet i/o", "", "write manifest cert packets to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + manifest_id m_id; + vector< manifest > certs; + + m_id = hexenc(args[0]); + app.db.get_manifest_certs(m_id, certs); + for (size_t i = 0; i < certs.size(); ++i) + pw.consume_manifest_cert(certs[i]); + guard.commit(); +} + +CMD(fcerts, "packet i/o", "", "write file cert packets to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + file_id f_id; + vector< file > certs; + + f_id = hexenc(args[0]); + app.db.get_file_certs(f_id, certs); + for (size_t i = 0; i < certs.size(); ++i) + pw.consume_file_cert(certs[i]); + guard.commit(); +} + +CMD(pubkey, "packet i/o", "", "write public key packet to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + rsa_keypair_id ident(args[0]); + base64< rsa_pub_key > key; + app.db.get_key(ident, key); + pw.consume_public_key(ident, key); + guard.commit(); +} + +CMD(privkey, "packet i/o", "", "write private key packet to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + rsa_keypair_id ident(args[0]); + base64< arc4 > key; + app.db.get_key(ident, key); + pw.consume_private_key(ident, key); + guard.commit(); +} + + +CMD(read, "packet i/o", "", "read packets from stdin") +{ + transaction_guard guard(app.db); + packet_db_writer dbw(app, true); + size_t count = read_packets(cin, dbw); + N(count != 0, "no packets found on stdin"); + if (count == 1) + P("read 1 packet\n"); + else + P("read %d packets\n", count); + guard.commit(); +} + + +CMD(agraph, "graph visualization", "", "dump ancestry graph to stdout") +{ + vector< manifest > certs; + transaction_guard guard(app.db); + app.db.get_manifest_certs(ancestor_cert_name, certs); + set nodes; + vector< pair > edges; + for(vector< manifest >::iterator i = certs.begin(); + i != certs.end(); ++i) + { + cert_value tv; + decode_base64(i->inner().value, tv); + nodes.insert(tv()); + nodes.insert(i->inner().ident()); + edges.push_back(make_pair(tv(), i->inner().ident())); + } + cout << "graph: " << endl << "{" << endl; // open graph + for (set::iterator i = nodes.begin(); i != nodes.end(); + ++i) + { + cout << "node: { title : \"" << *i << "\"}" << endl; + } + for (vector< pair >::iterator i = edges.begin(); i != edges.end(); + ++i) + { + cout << "edge: { sourcename : \"" << i->first << "\"" << endl + << " targetname : \"" << i->second << "\" }" << endl; + } + cout << "}" << endl << endl; // close graph + guard.commit(); +} + +CMD(fetch, "network", "[URL] [groupname]", "fetch recent changes from network") +{ + if (args.size() > 2) + throw usage(name); + + vector< pair > sources; + + if (args.size() == 0) + { + if (app.branch_name == "") + { + P("no branch name provided, fetching from all known URLs\n"); + app.db.get_all_known_sources(sources); + } + else + { + N(app.lua.hook_get_fetch_sources(app.branch_name, sources), + ("no URL / group pairs found for branch " + app.branch_name)); + } + } + else + { + N(args.size() == 2, "need URL and groupname"); + sources.push_back(make_pair(url(args[0]), + group(args[1]))); + } + + fetch_queued_blobs_from_network(sources, app); +} + +CMD(post, "network", "[URL] [groupname]", "post queued changes to network") +{ + if (args.size() > 2) + throw usage(name); + + vector< pair > targets; + if (args.size() == 0) + { + if (app.branch_name == "") + { + P("no branch name provided, posting all queued targets\n"); + app.db.get_queued_targets(targets); + } + else + { + N(app.lua.hook_get_post_targets(app.branch_name, targets), + ("no URL / group pairs found for branch " + app.branch_name)); + } + } + else + { + N(args.size() == 2, "need URL and groupname"); + targets.push_back(make_pair(url(args[0]), + group(args[1]))); + } + + post_queued_blobs_to_network(targets, app); +} + + +CMD(rcs_import, "rcs", " ...", "import all versions in RCS files") +{ + if (args.size() < 1) + throw usage(name); + + transaction_guard guard(app.db); + for (vector::const_iterator i = args.begin(); + i != args.end(); ++i) + { + import_rcs_file(fs::path(*i), app.db); + } + guard.commit(); +} + + +CMD(cvs_import, "rcs", "", "import all versions in CVS repository") +{ + if (args.size() != 1) + throw usage(name); + + import_cvs_repo(fs::path(args.at(0)), app); +} + + +}; // namespace commands ============================================================ --- tests/calculation_of_incorrect_unidiffs/secondfile 60fd4d4949d680b7ec23f41f610769802a464be1 +++ tests/calculation_of_incorrect_unidiffs/secondfile 60fd4d4949d680b7ec23f41f610769802a464be1 @@ -0,0 +1,1703 @@ +// copyright (C) 2002, 2003 graydon hoare +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "commands.hh" +#include "constants.hh" + +#include "app_state.hh" +#include "diff_patch.hh" +#include "file_io.hh" +#include "keys.hh" +#include "manifest.hh" +#include "network.hh" +#include "packet.hh" +#include "patch_set.hh" +#include "rcs_import.hh" +#include "sanity.hh" +#include "cert.hh" +#include "transforms.hh" +#include "update.hh" +#include "vocab.hh" +#include "work.hh" + +// +// this file defines the task-oriented "top level" commands which can be +// issued as part of a monotone command line. the command line can only +// have one such command on it, followed by a vector of strings which are its +// arguments. all --options will be processed by the main program *before* +// calling a command +// +// we might expose this blunt command interface to scripting someday. but +// not today. + +namespace commands +{ + struct command; + bool operator<(command const & self, command const & other); +}; + +namespace std +{ + template <> + struct std::greater + { + bool operator()(commands::command const * a, commands::command const * b) + { + return *a < *b; + } + }; +}; + +namespace commands +{ +using namespace std; + +struct command; + +static map cmds; + +struct command +{ + string name; + string cmdgroup; + string params; + string desc; + command(string const & n, + string const & g, + string const & p, + string const & d) : name(n), cmdgroup(g), params(p), desc(d) + { cmds[n] = this; } + virtual ~command() {} + virtual void exec(app_state & app, vector const & args) = 0; +}; + +bool operator<(command const & self, command const & other) +{ + return ((self.cmdgroup < other.cmdgroup) + || ((self.cmdgroup == other.cmdgroup) && (self.name < other.name))); +} + + +void explain_usage(string const & cmd, ostream & out) +{ + map::const_iterator i; + i = cmds.find(cmd); + if (i != cmds.end()) + { + out << " " << i->second->name << " " << i->second->params << endl + << " " << i->second->desc << endl << endl; + return; + } + + vector sorted; + out << "commands:" << endl; + for (i = cmds.begin(); i != cmds.end(); ++i) + { + sorted.push_back(i->second); + } + + sort(sorted.begin(), sorted.end(), std::greater()); + + string curr_group; + size_t col = 0; + size_t col2 = 0; + for (size_t i = 0; i < sorted.size(); ++i) + { + col2 = col2 > sorted[i]->cmdgroup.size() ? col2 : sorted[i]->cmdgroup.size(); + } + + for (size_t i = 0; i < sorted.size(); ++i) + { + if (sorted[i]->cmdgroup != curr_group) + { + curr_group = sorted[i]->cmdgroup; + out << endl; + out << " " << sorted[i]->cmdgroup; + col = sorted[i]->cmdgroup.size() + 2; + while (col++ < (col2 + 3)) + out << ' '; + } + out << " " << sorted[i]->name; + col += sorted[i]->name.size() + 1; + if (col >= 70) + { + out << endl; + col = 0; + while (col++ < (col2 + 3)) + out << ' '; + } + } + out << endl << endl; +} + +void process(app_state & app, string const & cmd, vector const & args) +{ + if (cmds.find(cmd) != cmds.end()) + { + L("executing %s command\n", cmd.c_str()); + cmds[cmd]->exec(app, args); + } + else + { + throw usage(cmd); + } +} + +#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) \ + +#define ALIAS(C, realcommand, group, params, desc) \ +CMD(C, group, params, desc) \ +{ \ + process(app, string(#realcommand), args); \ +} + + +static void ensure_bookdir() +{ + mkdir_p(local_path(book_keeping_dir)); +} + +static void get_manifest_path(local_path & m_path) +{ + m_path = (fs::path(book_keeping_dir) / fs::path(manifest_file_name)).string(); + L("manifest path is %s\n", m_path().c_str()); +} + +static void get_work_path(local_path & w_path) +{ + w_path = (fs::path(book_keeping_dir) / fs::path(work_file_name)).string(); + L("work path is %s\n", w_path().c_str()); +} + +static void get_manifest_map(manifest_map & m) +{ + ensure_bookdir(); + local_path m_path; + base64< gzip > m_data; + get_manifest_path(m_path); + if (file_exists(m_path)) + { + L("loading manifest file %s\n", m_path().c_str()); + read_data(m_path, m_data); + read_manifest_map(manifest_data(m_data), m); + L("read %d manifest entries\n", m.size()); + } + else + { + L("no manifest file %s\n", m_path().c_str()); + } +} + +static void put_manifest_map(manifest_map const & m) +{ + ensure_bookdir(); + local_path m_path; + manifest_data m_data; + get_manifest_path(m_path); + L("writing manifest file %s\n", m_path().c_str()); + write_manifest_map(m, m_data); + write_data(m_path, m_data.inner()); + L("wrote %d manifest entries\n", m.size()); +} + +static void get_work_set(work_set & w) +{ + ensure_bookdir(); + local_path w_path; + get_work_path(w_path); + if (file_exists(w_path)) + { + L("checking for un-committed work file %s\n", + w_path().c_str()); + data w_data; + read_data(w_path, w_data); + read_work_set(w_data, w); + L("read %d dels, %d adds from %s\n", + w.dels.size(), w.adds.size(), w_path().c_str()); + } + else + { + L("no un-committed work file %s\n", w_path().c_str()); + } +} + +static void put_work_set(work_set & w) +{ + local_path w_path; + get_work_path(w_path); + + if (w.adds.size() > 0 + || w.dels.size() > 0) + { + ensure_bookdir(); + data w_data; + write_work_set(w_data, w); + write_data(w_path, w_data); + } + else + { + delete_file(w_path); + } +} + +static void calculate_new_manifest_map(manifest_map const & m_old, + manifest_map & m_new) +{ + path_set paths; + work_set work; + extract_path_set(m_old, paths); + get_work_set(work); + if (work.dels.size() > 0) + L("removing %d dead files from manifest\n", + work.dels.size()); + if (work.adds.size() > 0) + L("adding %d files to manifest\n", work.adds.size()); + apply_work_set(work, paths); + build_manifest_map(paths, m_new); +} + +static string get_stdin() +{ + char buf[bufsz]; + string tmp; + while(cin) + { + cin.read(buf, bufsz); + tmp.append(buf, cin.gcount()); + } + return tmp; +} + +static void get_log_message(patch_set const & ps, + app_state & app, + string & log_message) +{ + string commentary; + string summary; + stringstream ss; + patch_set_to_text_summary(ps, ss); + summary = ss.str(); + commentary += "----------------------------------------------------------------------\n"; + commentary += "Enter Log. Lines beginning with `MT:' are removed automatically\n"; + commentary += "\n"; + commentary += "Summary of changes:\n"; + commentary += "\n"; + commentary += summary; + commentary += "----------------------------------------------------------------------\n"; + N(app.lua.hook_edit_comment(commentary, log_message), + "edit of log message failed"); +} + + +// the goal here is to look back through the ancestry of the provided +// child, checking to see the least ancestor it has which we received from +// the given network url/group pair. +// +// we use the ancestor as the source manifest when building a patchset to +// send to that url/group. + +static bool find_ancestor_on_netserver (manifest_id const & child, + url const & u, + group const & g, + manifest_id & anc, + app_state & app) +{ + set frontier; + cert_name tn(ancestor_cert_name); + frontier.insert(child); + + while (!frontier.empty()) + { + set next_frontier; + for (set::const_iterator i = frontier.begin(); + i != frontier.end(); ++i) + { + vector< manifest > tmp; + app.db.get_manifest_certs(*i, tn, tmp); + + // we go through this vector backwards because we would prefer to + // hit more recently-queued ancestors (such as intermediate nodes + // in a multi-node merge) rather than older ancestors. but of + // course, any ancestor will do. + + for (vector< manifest >::reverse_iterator j = tmp.rbegin(); + j != tmp.rend(); ++j) + { + cert_value tv; + decode_base64(j->inner().value, tv); + manifest_id anc_id (tv()); + + L("looking for parent %s of %s on server\n", + i->inner()().c_str(), + anc_id.inner()().c_str()); + + if (app.db.manifest_exists_on_netserver (u, g, anc_id)) + { + L("found parent %s on server\n", anc_id.inner()().c_str()); + anc = anc_id; + return true; + } + else + next_frontier.insert(anc_id); + } + } + + frontier = next_frontier; + } + + return false; +} + + +static void queue_edge_for_target_ancestor (pair const & targ, + manifest_id const & child_id, + manifest_map const & child_map, + app_state & app) +{ + // now here is an interesting thing: we *might* be sending data to a + // depot, or someone with indeterminate pre-existing state (say the first + // time we post to netnews), therefore we cannot just "send the edge" we + // just constructed in a merge or commit, we need to send an edge from a + // parent which we know to be present in the depot (or else an edge from + // the empty map -- full contents of all files). what is sent therefore + // changes on a depot-by-depot basis. this function calculates the + // appropriate thing to send. + // + // nb: this has no direct relation to what we store in our own + // database. we always store the edge from our parent, and we always know + // when we have a parent. + + vector< pair > one_target; + one_target.push_back(targ); + queueing_packet_writer qpw(app, one_target); + + manifest_data targ_ancestor_data; + manifest_map targ_ancestor_map; + manifest_id targ_ancestor_id; + + if (find_ancestor_on_netserver (child_id, + targ.first, + targ.second, + targ_ancestor_id, + app)) + { + app.db.get_manifest_version(targ_ancestor_id, targ_ancestor_data); + read_manifest_map(targ_ancestor_data, targ_ancestor_map); + } + + patch_set ps; + manifests_to_patch_set(targ_ancestor_map, child_map, app, ps); + patch_set_to_packets(ps, app, qpw); + + // now that we've queued the data, we can note this new child + // node as existing (well .. soon-to-exist) on the server + app.db.note_manifest_on_netserver (targ.first, targ.second, child_id); + +} + + +// this helper tries to produce merge <- mergeN(left,right), possibly +// merge3 if it can find an ancestor, otherwise merge2. it also queues the +// appropriate edges from known ancestors to the new merge node, to be +// transmitted to each of the targets provided. + +static void try_one_merge(manifest_id const & left, + manifest_id const & right, + manifest_id & merged, + app_state & app, + vector< pair > const & targets) +{ + manifest_data left_data, right_data, ancestor_data, merged_data; + manifest_map left_map, right_map, ancestor_map, merged_map; + manifest_id ancestor; + + app.db.get_manifest_version(left, left_data); + app.db.get_manifest_version(right, right_data); + read_manifest_map(left_data, left_map); + read_manifest_map(right_data, right_map); + + simple_merge_provider merger(app); + + if(find_common_ancestor(left, right, ancestor, app)) + { + P("common ancestor %s found, trying merge3\n", ancestor.inner()().c_str()); + app.db.get_manifest_version(ancestor, ancestor_data); + read_manifest_map(ancestor_data, ancestor_map); + N(merge3(ancestor_map, left_map, right_map, + app, merger, merged_map), + (string("failed to merge manifests ") + + left.inner()() + " and " + right.inner()())); + } + else + { + P("no common ancestor found, trying merge2\n"); + N(merge2(left_map, right_map, app, merger, merged_map), + (string("failed to merge manifests ") + + left.inner()() + " and " + right.inner()())); + } + + write_manifest_map(merged_map, merged_data); + calculate_manifest_map_ident(merged_map, merged); + + base64< gzip > left_edge; + diff(left_data.inner(), merged_data.inner(), left_edge); + + // FIXME: we do *not* manufacture or store the second edge to + // the merged version, since doing so violates the + // assumptions of the db, and the 'right' version already + // exists in its entirety, anyways. this is a subtle issue + // though and I'm not sure I'm making the right + // decision. revisit. if you do not see that it is a subtle + // issue I suggest you are not thinking about it long enough. + // + // base64< gzip > right_edge; + // diff(right_data.inner(), merged_data.inner(), right_edge); + // app.db.put_manifest_version(right, merged, right_edge); + + + // we do of course record the left edge, and ancestry relationship to + // both predecessors. + + { + packet_db_writer dbw(app); + + dbw.consume_manifest_delta(left, merged, left_edge); + cert_manifest_ancestor(left, merged, app, dbw); + cert_manifest_ancestor(right, merged, app, dbw); + cert_manifest_date_now(merged, app, dbw); + cert_manifest_author_default(merged, app, dbw); + + // make sure the appropriate edges get queued for the network. + for (vector< pair >::const_iterator targ = targets.begin(); + targ != targets.end(); ++targ) + { + queue_edge_for_target_ancestor (*targ, merged, merged_map, app); + } + + queueing_packet_writer qpw(app, targets); + cert_manifest_ancestor(left, merged, app, qpw); + cert_manifest_ancestor(right, merged, app, qpw); + cert_manifest_date_now(merged, app, qpw); + cert_manifest_author_default(merged, app, qpw); + } + +} + + +// actual commands follow + +CMD(lscerts, "key and cert", "(file|manifest) ", + "list certs associated with manifest or file") +{ + if (args.size() != 2) + throw usage(name); + + vector certs; + + transaction_guard guard(app.db); + + if (args[0] == "manifest") + { + manifest_id ident(args[1]); + vector< manifest > ts; + app.db.get_manifest_certs(ident, ts); + for (size_t i = 0; i < ts.size(); ++i) + certs.push_back(ts[i].inner()); + } + else if (args[0] == "file") + { + file_id ident(args[1]); + vector< file > ts; + app.db.get_file_certs(ident, ts); + for (size_t i = 0; i < ts.size(); ++i) + certs.push_back(ts[i].inner()); + } + else + throw usage(name); + + for (size_t i = 0; i < certs.size(); ++i) + { + bool ok = check_cert(app, certs[i]); + cert_value tv; + decode_base64(certs[i].value, tv); + string washed; + if (guess_binary(tv())) + { + washed = ""; + } + else + { + washed = tv(); + } + string head = string(ok ? "ok sig from " : "bad sig from ") + + "[" + certs[i].key() + "] : " + + "[" + certs[i].name() + "] = ["; + string pad(head.size(), ' '); + vector lines; + split_into_lines(washed, lines); + I(lines.size() > 0); + cout << head << lines[0] ; + for (size_t i = 1; i < lines.size(); ++i) + cout << endl << pad << lines[i]; + cout << "]" << endl; + } + guard.commit(); +} + +CMD(lskeys, "key and cert", "[partial-id]", "list keys") +{ + vector pubkeys; + vector privkeys; + + transaction_guard guard(app.db); + + if (args.size() == 0) + app.db.get_key_ids("", pubkeys, privkeys); + else if (args.size() == 1) + app.db.get_key_ids(args[0], pubkeys, privkeys); + else + throw usage(name); + + if (pubkeys.size() > 0) + { + cout << endl << "[public keys]" << endl; + for (size_t i = 0; i < pubkeys.size(); ++i) + cout << pubkeys[i]() << endl; + cout << endl; + } + + if (privkeys.size() > 0) + { + cout << endl << "[private keys]" << endl; + for (size_t i = 0; i < privkeys.size(); ++i) + cout << privkeys[i]() << endl; + cout << endl; + } + + guard.commit(); +} + +CMD(genkey, "key and cert", "", "generate an RSA key-pair") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + rsa_keypair_id ident(args[0]); + + N(! app.db.key_exists(ident), + (string("key '") + ident() + "' already exists in database")); + + base64 pub; + base64< arc4 > priv; + P("generating key-pair '%s'\n", ident().c_str()); + generate_key_pair(app.lua, ident, pub, priv); + P("storing key-pair '%s' in database\n", ident().c_str()); + app.db.put_key_pair(ident, pub, priv); + + guard.commit(); +} + +CMD(cert, "key and cert", "(file|manifest) [certval]", + "create a cert for a file or manifest") +{ + if ((args.size() != 4) && (args.size() != 3)) + throw usage(name); + + transaction_guard guard(app.db); + + hexenc ident(args[1]); + cert_name name(args[2]); + + rsa_keypair_id key; + if (app.signing_key() != "") + key = app.signing_key; + else + N(guess_default_key(key, app), + "no unique private key found, and no key specified"); + + cert_value val; + if (args.size() == 4) + val = cert_value(args[3]); + else + val = cert_value(get_stdin()); + + base64 val_encoded; + encode_base64(val, val_encoded); + + cert t(ident, name, val_encoded, key); + + // nb: we want to throw usage on mis-use *before* asking for a + // passphrase. + + if (args[0] == "file") + { + calculate_cert(app, t); + app.db.put_file_cert(file(t)); + } + else if (args[0] == "manifest") + { + calculate_cert(app, t); + app.db.put_manifest_cert(manifest(t)); + } + else + throw usage(this->name); + + guard.commit(); +} + + +CMD(tag, "certificate", " ", + "put a symbolic tag cert on a manifest version") +{ + if (args.size() != 2) + throw usage(name); + manifest_id m(args[0]); + packet_db_writer dbw(app); + cert_manifest_tag(m, args[1], app, dbw); +} + +CMD(approve, "certificate", "(file|manifest) ", + "approve of a manifest or file version") +{ + if (args.size() != 2) + throw usage(name); + if (args[0] == "manifest") + { + manifest_id m(args[1]); + packet_db_writer dbw(app); + cert_manifest_approval(m, true, app, dbw); + } + else if (args[0] == "file") + { + packet_db_writer dbw(app); + file_id f(args[1]); + cert_file_approval(f, true, app, dbw); + } + else + throw usage(name); +} + +CMD(disapprove, "certificate", "(file|manifest) ", + "disapprove of a manifest or file version") +{ + if (args.size() != 2) + throw usage(name); + if (args[0] == "manifest") + { + manifest_id m(args[1]); + packet_db_writer dbw(app); + cert_manifest_approval(m, false, app, dbw); + } + else if (args[0] == "file") + { + file_id f(args[1]); + packet_db_writer dbw(app); + cert_file_approval(f, false, app, dbw); + } + else + throw usage(name); +} + + +CMD(comment, "certificate", "(file|manifest) [comment]", + "comment on a file or manifest version") +{ + if (args.size() != 2 && args.size() != 3) + throw usage(name); + + string comment; + if (args.size() == 3) + comment = args[2]; + else + N(app.lua.hook_edit_comment("", comment), "edit comment failed"); + + N(comment.find_first_not_of(" \r\t\n") == string::npos, "empty comment"); + + if (args[0] == "file") + { + packet_db_writer dbw(app); + cert_file_comment(file_id(args[1]), comment, app, dbw); + } + else if (args[0] == "manifest") + { + packet_db_writer dbw(app); + cert_manifest_comment(manifest_id(args[1]), comment, app, dbw); + } + else + throw usage(name); +} + + + +CMD(add, "working copy", " [...]", "add files to working copy") +{ + if (args.size() < 1) + throw usage(name); + + transaction_guard guard(app.db); + + manifest_map man; + work_set work; + get_manifest_map(man); + get_work_set(work); + bool rewrite_work = false; + + for (vector::const_iterator i = args.begin(); i != args.end(); ++i) + build_addition(file_path(*i), app, work, man, rewrite_work); + + guard.commit(); + + // small race here + if (rewrite_work) + put_work_set(work); +} + +CMD(drop, "working copy", " [...]", "drop files from working copy") +{ + if (args.size() < 1) + throw usage(name); + + manifest_map man; + work_set work; + get_manifest_map(man); + get_work_set(work); + bool rewrite_work = false; + + transaction_guard guard(app.db); + + for (vector::const_iterator i = args.begin(); i != args.end(); ++i) + build_deletion(file_path(*i), app, work, man, rewrite_work); + + guard.commit(); + + // small race here + if (rewrite_work) + put_work_set(work); +} + +CMD(commit, "working copy", "[log message]", "commit working copy to database") +{ + string log_message(""); + manifest_map m_old, m_new; + patch_set ps; + + get_manifest_map(m_old); + calculate_new_manifest_map(m_old, m_new); + manifest_id old_id, new_id; + calculate_manifest_map_ident(m_old, old_id); + calculate_manifest_map_ident(m_new, new_id); + + if (args.size() != 0 && args.size() != 1) + throw usage(name); + + cert_value branchname; + if (app.branch_name != "") + { + branchname = app.branch_name; + } + else + { + vector< manifest > certs; + cert_name branch(branch_cert_name); + app.db.get_manifest_certs(old_id, branch, certs); + + N(certs.size() != 0, + string("no branch certs found for old manifest ") + + old_id.inner()() + ", please provide a branch name"); + + N(certs.size() == 1, + string("multiple branch certs found for old manifest ") + + old_id.inner()() + ", please provide a branch name"); + + decode_base64(certs[0].inner().value, branchname); + } + + L("committing %s to branch %s\n", + new_id.inner()().c_str(), branchname().c_str()); + app.branch_name = branchname(); + + manifests_to_patch_set(m_old, m_new, app, ps); + + // get log message + if (args.size() == 1) + log_message = args[0]; + else + get_log_message(ps, app, log_message); + + N(log_message.find_first_not_of(" \r\t\n") != string::npos, + "empty log message"); + + { + transaction_guard guard(app.db); + + // process manifest delta or new manifest + if (app.db.manifest_version_exists(ps.m_new)) + { + L("skipping manifest %s, already in database\n", ps.m_new.inner()().c_str()); + } + else + { + if (app.db.manifest_version_exists(ps.m_old)) + { + L("inserting manifest delta %s -> %s\n", + ps.m_old.inner()().c_str(), ps.m_new.inner()().c_str()); + manifest_data m_old_data, m_new_data; + app.db.get_manifest_version(ps.m_old, m_old_data); + write_manifest_map(m_new, m_new_data); + base64< gzip > del; + diff(m_old_data.inner(), m_new_data.inner(), del); + app.db.put_manifest_version(ps.m_old, ps.m_new, manifest_delta(del)); + } + else + { + L("inserting full manifest %s\n", + ps.m_new.inner()().c_str()); + manifest_data m_new_data; + write_manifest_map(m_new, m_new_data); + app.db.put_manifest(ps.m_new, m_new_data); + } + } + + // process file deltas + for (set::const_iterator i = ps.f_deltas.begin(); + i != ps.f_deltas.end(); ++i) + { + if (app.db.file_version_exists(i->id_new)) + { + L("skipping file delta %s, already in database\n", i->id_new.inner()().c_str()); + } + else + { + if (app.db.file_version_exists(i->id_old)) + { + L("inserting delta %s -> %s\n", + i->id_old.inner()().c_str(), i->id_new.inner()().c_str()); + file_data old_data; + base64< gzip > new_data; + app.db.get_file_version(i->id_old, old_data); + read_data(i->path, new_data); + base64< gzip > del; + diff(old_data.inner(), new_data, del); + app.db.put_file_version(i->id_old, i->id_new, file_delta(del)); + } + else + { + L("inserting full version %s\n", i->id_old.inner()().c_str()); + base64< gzip > new_data; + read_data(i->path, new_data); + // sanity check + hexenc tid; + calculate_ident(new_data, tid); + I(tid == i->id_new.inner()); + app.db.put_file(i->id_new, file_data(new_data)); + } + } + } + + // process file adds + for (set::const_iterator i = ps.f_adds.begin(); + i != ps.f_adds.end(); ++i) + { + if (app.db.file_version_exists(i->ident)) + { + L("skipping file %s %s, already in database\n", + i->path().c_str(), i->ident.inner()().c_str()); + } + else + { + // it's a new file + L("inserting new file %s %s\n", + i->path().c_str(), i->ident.inner()().c_str()); + base64< gzip > new_data; + read_data(i->path, new_data); + app.db.put_file(i->ident, new_data); + } + } + + packet_db_writer dbw(app); + + if (! m_old.empty()) + cert_manifest_ancestor(ps.m_old, ps.m_new, app, dbw); + + cert_manifest_in_branch(ps.m_new, branchname, app, dbw); + cert_manifest_date_now(ps.m_new, app, dbw); + cert_manifest_author_default(ps.m_new, app, dbw); + cert_manifest_changelog(ps.m_new, log_message, app, dbw); + + // commit done, now queue diff for sending + + if (app.db.manifest_version_exists(ps.m_new)) + { + vector< pair > targets; + app.lua.hook_get_post_targets(branchname, targets); + + // make sure the appropriate edges get queued for the network. + for (vector< pair >::const_iterator targ = targets.begin(); + targ != targets.end(); ++targ) + { + queue_edge_for_target_ancestor (*targ, ps.m_new, m_new, app); + } + + // throw in all available certs for good measure + queueing_packet_writer qpw(app, targets); + vector< manifest > certs; + app.db.get_manifest_certs(ps.m_new, certs); + for(vector< manifest >::const_iterator i = certs.begin(); + i != certs.end(); ++i) + qpw.consume_manifest_cert(*i); + } + + guard.commit(); + } + // small race condition here... + local_path w_path; + get_work_path(w_path); + delete_file(w_path); + put_manifest_map(m_new); + P("committed %s\n", ps.m_new.inner()().c_str()); +} + +CMD(update, "working copy", "[sort keys...]", "update working copy, relative to sorting keys") +{ + + manifest_data m_chosen_data; + manifest_map m_old, m_working, m_chosen, m_new; + manifest_id m_old_id, m_chosen_id; + + transaction_guard guard(app.db); + + get_manifest_map(m_old); + calculate_manifest_map_ident(m_old, m_old_id); + calculate_new_manifest_map(m_old, m_working); + + pick_update_target(m_old_id, args, app, m_chosen_id); + P("selected update target %s\n", + m_chosen_id.inner()().c_str()); + app.db.get_manifest_version(m_chosen_id, m_chosen_data); + read_manifest_map(m_chosen_data, m_chosen); + + update_merge_provider merger(app); + N(merge3(m_old, m_chosen, m_working, app, merger, m_new), + string("manifest merge failed, no update performed")); + + P("calculating patchset for update\n"); + patch_set ps; + manifests_to_patch_set(m_working, m_new, app, ps); + + L("applying %d deletions to files in tree\n", ps.f_dels.size()); + for (set::const_iterator i = ps.f_dels.begin(); + i != ps.f_dels.end(); ++i) + { + L("deleting %s\n", (*i)().c_str()); + delete_file(*i); + } + + L("applying %d moves to files in tree\n", ps.f_moves.size()); + for (set::const_iterator i = ps.f_moves.begin(); + i != ps.f_moves.end(); ++i) + { + L("moving %s -> %s\n", i->path_old().c_str(), i->path_new().c_str()); + move_file(i->path_old, i->path_new); + } + + L("applying %d additions to tree\n", ps.f_adds.size()); + for (set::const_iterator i = ps.f_adds.begin(); + i != ps.f_adds.end(); ++i) + { + L("adding %s as %s\n", i->ident.inner()().c_str(), i->path().c_str()); + file_data tmp; + if (app.db.file_version_exists(i->ident)) + app.db.get_file_version(i->ident, tmp); + else if (merger.temporary_store.find(i->ident) != merger.temporary_store.end()) + tmp = merger.temporary_store[i->ident]; + else + I(false); // trip assert. this should be impossible. + write_data(i->path, tmp.inner()); + } + + L("applying %d deltas to tree\n", ps.f_deltas.size()); + for (set::const_iterator i = ps.f_deltas.begin(); + i != ps.f_deltas.end(); ++i) + { + P("updating file %s: %s -> %s\n", + i->path().c_str(), + i->id_old.inner()().c_str(), + i->id_new.inner()().c_str()); + + // sanity check + { + base64< gzip > dtmp; + hexenc dtmp_id; + read_data(i->path, dtmp); + calculate_ident(dtmp, dtmp_id); + I(dtmp_id == i->id_old.inner()); + } + + // ok, replace with new version + { + file_data tmp; + if (app.db.file_version_exists(i->id_new)) + app.db.get_file_version(i->id_new, tmp); + else if (merger.temporary_store.find(i->id_new) != merger.temporary_store.end()) + tmp = merger.temporary_store[i->id_new]; + else + I(false); // trip assert. this should be impossible. + write_data(i->path, tmp.inner()); + } + } + + L("update successful\n"); + guard.commit(); + + // small race condition here... + // nb: we write out m_chosen, not m_new, because the manifest-on-disk + // is the basis of the working copy, not the working copy itself. + put_manifest_map(m_chosen); + P("updated to base version %s\n", m_chosen_id.inner()().c_str()); +} + + + +CMD(cat, "tree", "(file|manifest) ", "write file or manifest from database to stdout") +{ + if (args.size() != 2) + throw usage(name); + + transaction_guard guard(app.db); + + if (args[0] == "file") + { + file_data dat; + file_id ident(args[1]); + + N(app.db.file_version_exists(ident), + (string("no file version ") + ident.inner()() + " found in database")); + + L("dumping file %s\n", ident.inner()().c_str()); + app.db.get_file_version(ident, dat); + data unpacked; + unpack(dat.inner(), unpacked); + cout.write(unpacked().data(), unpacked().size()); + + } + else if (args[0] == "manifest") + { + manifest_data dat; + manifest_id ident(args[1]); + + N(app.db.manifest_version_exists(ident), + (string("no file version ") + ident.inner()() + " found in database")); + + L("dumping manifest %s\n", ident.inner()().c_str()); + app.db.get_manifest_version(ident, dat); + data unpacked; + unpack(dat.inner(), unpacked); + cout.write(unpacked().data(), unpacked().size()); + } + else + throw usage(name); + + guard.commit(); +} + + +CMD(checkout, "tree", "", "check out tree state from database") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + + file_data data; + manifest_id ident(args[0]); + manifest_map m; + + N(app.db.manifest_version_exists(ident), + (string("no manifest version ") + ident.inner()() + " found in database")); + + L("exporting manifest %s\n", ident.inner()().c_str()); + manifest_data m_data; + app.db.get_manifest_version(ident, m_data); + read_manifest_map(m_data, m); + put_manifest_map(m); + + for (manifest_map::const_iterator i = m.begin(); i != m.end(); ++i) + { + vector args; + path_id_pair pip(*i); + + N(app.db.file_version_exists(pip.ident()), + (string("no file version ") + + pip.ident().inner()() + + " found in database for " + + pip.path()().c_str())); + + file_data dat; + L("writing file %s to %s\n", + pip.ident().inner()().c_str(), + pip.path()().c_str()); + app.db.get_file_version(pip.ident(), dat); + write_data(pip.path(), dat.inner()); + } + + guard.commit(); +} + +ALIAS(co, checkout, "tree", "", + "check out tree state from database; alias for checkout") + +CMD(heads, "tree", "", "show unmerged heads of branch") +{ + vector heads; + if (args.size() != 0) + throw usage(name); + + if (app.branch_name == "") + { + cout << "please specify a branch, with --branch=" << endl; + return; + } + + get_branch_heads(app.branch_name, app, heads); + + if (heads.size() == 0) + cout << "branch '" << app.branch_name << "' is empty" << endl; + else if (heads.size() == 1) + cout << "branch '" << app.branch_name << "' is currently merged:" << endl; + else + cout << "branch '" << app.branch_name << "' is currently unmerged:" << endl; + + for (vector::const_iterator i = heads.begin(); + i != heads.end(); ++i) + { + cout << i->inner()() << endl; + } +} + + +CMD(merge, "tree", "", "merge unmerged heads of branch") +{ + + vector heads; + + if (args.size() != 0) + throw usage(name); + + if (app.branch_name == "") + { + cout << "please specify a branch, with --branch=" << endl; + return; + } + + get_branch_heads(app.branch_name, app, heads); + + if (heads.size() == 0) + { + cout << "branch " << args[0] << "is empty" << endl; + return; + } + else if (heads.size() == 1) + { + cout << "branch " << args[0] << "is merged" << endl; + return; + } + else + { + vector< pair > targets; + app.lua.hook_get_post_targets(app.branch_name, targets); + + manifest_id left = heads[0]; + manifest_id ancestor; + for (size_t i = 1; i < heads.size(); ++i) + { + manifest_id right = heads[i]; + P("merging with manifest %d / %d: %s <-> %s\n", + i, heads.size(), + left.inner()().c_str(), right.inner()().c_str()); + + manifest_id merged; + transaction_guard guard(app.db); + try_one_merge (left, right, merged, app, targets); + + // merged 1 edge; now we commit this, update merge source and + // try next one + + packet_db_writer dbw(app); + queueing_packet_writer qpw(app, targets); + cert_manifest_in_branch(merged, app.branch_name, app, dbw); + cert_manifest_in_branch(merged, app.branch_name, app, qpw); + + string log = "merge of " + left.inner()() + " and " + right.inner()(); + cert_manifest_changelog(merged, log, app, dbw); + cert_manifest_changelog(merged, log, app, qpw); + + guard.commit(); + P("[source] %s\n[source] %s\n[merged] %s\n", + left.inner()().c_str(), + right.inner()().c_str(), + merged.inner()().c_str()); + left = merged; + } + } +} + + +CMD(propagate, "tree", " ", + "merge from one branch to another asymmetrically") +{ + /* + + this is a special merge operator, but very useful for people maintaining + "slightly disparate but related" trees. it does a one-way merge; less + powerful than putting things in the same branch and also more flexible. + + 1. check to see if src and dst branches are merged, if not abort, if so + call heads N1 and N2 respectively. + + 2. (FIXME: not yet present) run the hook propagate ("src-branch", + "dst-branch", N1, N2) which gives the user a chance to massage N1 into + a state which is likely to "merge nicely" with N2, eg. edit pathnames, + omit optional files of no interest. + + 3. do a normal 2 or 3-way merge on N1 and N2, depending on the + existence of common ancestors. + + 4. save the results as the delta (N2,M), the ancestry edges (N1,M) + and (N2,M), and the cert (N2,dst). + + 5. queue the resulting packets to send to the url for dst-branch, not + src-branch. + + */ + + vector src_heads, dst_heads; + + if (args.size() != 2) + throw usage(name); + + get_branch_heads(args[0], app, src_heads); + get_branch_heads(args[1], app, dst_heads); + + if (src_heads.size() == 0) + { + cout << "branch " << args[0] << "is empty" << endl; + return; + } + else if (src_heads.size() != 1) + { + cout << "branch " << args[0] << "is not merged" << endl; + return; + } + else if (dst_heads.size() == 0) + { + cout << "branch " << args[1] << "is empty" << endl; + return; + } + else if (dst_heads.size() != 1) + { + cout << "branch " << args[1] << "is not merged" << endl; + return; + } + else + { + vector< pair > targets; + app.lua.hook_get_post_targets(args[1], targets); + + manifest_id merged; + transaction_guard guard(app.db); + try_one_merge (src_heads[0], dst_heads[0], merged, app, targets); + + queueing_packet_writer qpw(app, targets); + cert_manifest_in_branch(merged, app.branch_name, app, qpw); + cert_manifest_changelog(merged, + "propagate of " + + src_heads[0].inner()() + + " and " + + dst_heads[0].inner()() + + "\n" + + "from branch " + + args[0] + " to " + args[1] + "\n", + app, qpw); + guard.commit(); + } +} + + + +CMD(diff, "informative", "", "show current diffs on stdout") +{ + manifest_map m_old, m_new; + patch_set ps; + + transaction_guard guard(app.db); + + if (args.size() > 0) + { + manifest_data dat; + manifest_id ident(args[0]); + + N(app.db.manifest_version_exists(ident), + (string("no file version ") + ident.inner()() + " found in database")); + + L("getting manifest %s\n", ident.inner()().c_str()); + app.db.get_manifest_version(ident, dat); + read_manifest_map(dat, m_old); + } + else + get_manifest_map(m_old); + + if (args.size() > 1) + { + manifest_data dat; + manifest_id ident(args[1]); + + N(app.db.manifest_version_exists(ident), + (string("no file version ") + ident.inner()() + " found in database")); + + L("getting manifest %s\n", ident.inner()().c_str()); + app.db.get_manifest_version(ident, dat); + read_manifest_map(dat, m_new); + } + else + calculate_new_manifest_map(m_old, m_new); + + manifests_to_patch_set(m_old, m_new, app, ps); + + for (set::const_iterator i = ps.f_deltas.begin(); + i != ps.f_deltas.end(); ++i) + { + file_data f_old, f_new; + gzip decoded_old, decoded_new; + data decompressed_old, decompressed_new; + vector old_lines, new_lines; + + app.db.get_file_version(i->id_old, f_old); + decode_base64(f_old.inner(), decoded_old); + decode_gzip(decoded_old, decompressed_old); + + if (args.size() > 1) + { + app.db.get_file_version(i->id_new, f_new); + decode_base64(f_new.inner(), decoded_new); + decode_gzip(decoded_new, decompressed_new); + } + else + read_data(i->path, decompressed_new); + + split_into_lines(decompressed_old(), old_lines); + split_into_lines(decompressed_new(), new_lines); + + unidiff(i->path(), i->path(), old_lines, new_lines, cout); + } + guard.commit(); +} + +CMD(status, "informative", "", "show status of working copy") +{ + manifest_map m_old, m_new; + patch_set ps; + + transaction_guard guard(app.db); + get_manifest_map(m_old); + calculate_new_manifest_map(m_old, m_new); + manifests_to_patch_set(m_old, m_new, app, ps); + patch_set_to_text_summary(ps, cout); + guard.commit(); +} + + +CMD(mdelta, "packet i/o", " ", "write manifest delta packet to stdout") +{ + if (args.size() != 2) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + manifest_id m_old_id, m_new_id; + manifest_data m_old_data, m_new_data; + manifest_map m_old, m_new; + patch_set ps; + m_old_id = hexenc(args[0]); + m_new_id = hexenc(args[1]); + app.db.get_manifest_version(m_old_id, m_old_data); + app.db.get_manifest_version(m_new_id, m_new_data); + read_manifest_map(m_old_data, m_old); + read_manifest_map(m_new_data, m_new); + manifests_to_patch_set(m_old, m_new, app, ps); + patch_set_to_packets(ps, app, pw); + guard.commit(); +} + +CMD(fdelta, "packet i/o", " ", "write file delta packet to stdout") +{ + if (args.size() != 2) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + file_id f_old_id, f_new_id; + file_data f_old_data, f_new_data; + f_old_id = hexenc(args[0]); + f_new_id = hexenc(args[1]); + app.db.get_file_version(f_old_id, f_old_data); + app.db.get_file_version(f_new_id, f_new_data); + base64< gzip > del; + diff(f_old_data.inner(), f_new_data.inner(), del); + pw.consume_file_delta(f_old_id, f_new_id, file_delta(del)); + guard.commit(); +} + +CMD(mdata, "packet i/o", "", "write manifest data packet to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + manifest_id m_id; + manifest_data m_data; + m_id = hexenc(args[0]); + app.db.get_manifest_version(m_id, m_data); + pw.consume_manifest_data(m_id, m_data); + guard.commit(); +} + + +CMD(fdata, "packet i/o", "", "write file data packet to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + file_id f_id; + file_data f_data; + f_id = hexenc(args[0]); + app.db.get_file_version(f_id, f_data); + pw.consume_file_data(f_id, f_data); + guard.commit(); +} + +CMD(mcerts, "packet i/o", "", "write manifest cert packets to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + manifest_id m_id; + vector< manifest > certs; + + m_id = hexenc(args[0]); + app.db.get_manifest_certs(m_id, certs); + for (size_t i = 0; i < certs.size(); ++i) + pw.consume_manifest_cert(certs[i]); + guard.commit(); +} + +CMD(fcerts, "packet i/o", "", "write file cert packets to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + + file_id f_id; + vector< file > certs; + + f_id = hexenc(args[0]); + app.db.get_file_certs(f_id, certs); + for (size_t i = 0; i < certs.size(); ++i) + pw.consume_file_cert(certs[i]); + guard.commit(); +} + +CMD(pubkey, "packet i/o", "", "write public key packet to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + rsa_keypair_id ident(args[0]); + base64< rsa_pub_key > key; + app.db.get_key(ident, key); + pw.consume_public_key(ident, key); + guard.commit(); +} + +CMD(privkey, "packet i/o", "", "write private key packet to stdout") +{ + if (args.size() != 1) + throw usage(name); + + transaction_guard guard(app.db); + packet_writer pw(cout); + rsa_keypair_id ident(args[0]); + base64< arc4 > key; + app.db.get_key(ident, key); + pw.consume_private_key(ident, key); + guard.commit(); +} + + +CMD(read, "packet i/o", "", "read packets from stdin") +{ + transaction_guard guard(app.db); + packet_db_writer dbw(app, true); + size_t count = read_packets(cin, dbw); + N(count != 0, "no packets found on stdin"); + if (count == 1) + P("read 1 packet\n"); + else + P("read %d packets\n", count); + guard.commit(); +} + + +CMD(agraph, "graph visualization", "", "dump ancestry graph to stdout") +{ + vector< manifest > certs; + transaction_guard guard(app.db); + app.db.get_manifest_certs(ancestor_cert_name, certs); + set nodes; + vector< pair > edges; + for(vector< manifest >::iterator i = certs.begin(); + i != certs.end(); ++i) + { + cert_value tv; + decode_base64(i->inner().value, tv); + nodes.insert(tv()); + nodes.insert(i->inner().ident()); + edges.push_back(make_pair(tv(), i->inner().ident())); + } + cout << "graph: " << endl << "{" << endl; // open graph + for (set::iterator i = nodes.begin(); i != nodes.end(); + ++i) + { + cout << "node: { title : \"" << *i << "\"}" << endl; + } + for (vector< pair >::iterator i = edges.begin(); i != edges.end(); + ++i) + { + cout << "edge: { sourcename : \"" << i->first << "\"" << endl + << " targetname : \"" << i->second << "\" }" << endl; + } + cout << "}" << endl << endl; // close graph + guard.commit(); +} + +CMD(fetch, "network", "[URL] [groupname]", "fetch recent changes from network") +{ + if (args.size() > 2) + throw usage(name); + + vector< pair > sources; + + if (args.size() == 0) + { + if (app.branch_name == "") + { + P("no branch name provided, fetching from all known URLs\n"); + app.db.get_all_known_sources(sources); + } + else + { + N(app.lua.hook_get_fetch_sources(app.branch_name, sources), + ("no URL / group pairs found for branch " + app.branch_name)); + } + } + else + { + N(args.size() == 2, "need URL and groupname"); + sources.push_back(make_pair(url(args[0]), + group(args[1]))); + } + + fetch_queued_blobs_from_network(sources, app); +} + +CMD(post, "network", "[URL] [groupname]", "post queued changes to network") +{ + if (args.size() > 2) + throw usage(name); + + vector< pair > targets; + if (args.size() == 0) + { + if (app.branch_name == "") + { + P("no branch name provided, posting all queued targets\n"); + app.db.get_queued_targets(targets); + } + else + { + N(app.lua.hook_get_post_targets(app.branch_name, targets), + ("no URL / group pairs found for branch " + app.branch_name)); + } + } + else + { + N(args.size() == 2, "need URL and groupname"); + targets.push_back(make_pair(url(args[0]), + group(args[1]))); + } + + post_queued_blobs_to_network(targets, app); +} + + +CMD(rcs_import, "rcs", " ...", "import all versions in RCS files") +{ + if (args.size() < 1) + throw usage(name); + + transaction_guard guard(app.db); + for (vector::const_iterator i = args.begin(); + i != args.end(); ++i) + { + import_rcs_file(fs::path(*i), app.db); + } + guard.commit(); +} + + +CMD(cvs_import, "rcs", "", "import all versions in CVS repository") +{ + if (args.size() != 1) + throw usage(name); + + import_cvs_repo(fs::path(args.at(0)), app); +} + + +}; // namespace commands ============================================================ --- tests/delete_work_file_on_checkout/__driver__.lua 83bdf136c65453873abd9b44139032aaa27c2eb6 +++ tests/delete_work_file_on_checkout/__driver__.lua 83bdf136c65453873abd9b44139032aaa27c2eb6 @@ -0,0 +1,12 @@ + +mtn_setup() + +addfile("testfile0", "version 0 of first test file\n") +writefile("testfile1", "version 1 of second test file\n") +commit() +v1 = base_revision() +addfile("testfile1") +check(exists("_MTN/work")) +remove_recursive("_MTN") +check(cmd(mtn("checkout", "--revision", v1, ".")), 0, false, false) +check(not exists("_MTN/work")) ============================================================ --- tests/drop_missing_and_unknown_files/__driver__.lua c13655b9cd05d58f28cf28c410a117cc25b09292 +++ tests/drop_missing_and_unknown_files/__driver__.lua c13655b9cd05d58f28cf28c410a117cc25b09292 @@ -0,0 +1,33 @@ + +mtn_setup() + +mkdir("places") +addfile("maude", "the file maude") +addfile("harold", "the file harold") +addfile("places/cemetery", "the place file cemetery") +commit() + +os.remove("maude") + +check(cmd(mtn("drop", "maude")), 0, false, true) +check(qgrep('dropping maude from workspace manifest', "stderr")) + +check(cmd(mtn("status")), 0, true) +check(qgrep("maude", "stdout")) +check(not qgrep("harold", "stdout")) +check(not qgrep("places/cemetery", "stdout")) + +check(cmd(mtn("drop", "foobar")), 0, false, true) +check(qgrep("skipping foobar", "stderr")) + +os.remove("harold") +os.remove("places/cemetery") + +check(cmd(mtn("drop", "--missing")), 0, false, true) +check(qgrep('dropping harold from workspace manifest', "stderr")) +check(qgrep('dropping places/cemetery from workspace manifest', "stderr")) + +check(cmd(mtn("status")), 0, true) +check(qgrep("maude", "stdout")) +check(qgrep("harold", "stdout")) +check(qgrep("places/cemetery", "stdout")) ============================================================ --- tests/merging_adds/__driver__.lua dfb2cf3a556ab1477e77e79d064a4b74f2857bea +++ tests/merging_adds/__driver__.lua dfb2cf3a556ab1477e77e79d064a4b74f2857bea @@ -0,0 +1,27 @@ + +mtn_setup() + +addfile("irrelevantfile", "this is just a file\n") +commit() +anc = base_revision() + +addfile("testfile1", "This is test file 1\n") +commit() +left = base_revision() + +revert_to(anc) + +addfile("testfile2", "This is test file 2\n") +commit() +right = base_revision() + +check(cmd(mtn("--branch=testbranch", "merge")), 0, false, false) +check(cmd(mtn("update")), 0, false, false) + +writefile("expected_irrelevant", "this is just a file\n") +writefile("expected_data1", "This is test file 1\n") +writefile("expected_data2", "This is test file 2\n") + +check(samefile("irrelevantfile", "expected_irrelevant")) +check(samefile("testfile1", "expected_data1")) +check(samefile("testfile2", "expected_data2")) ============================================================ --- tests/merging_adds_in_unrelated_revisions/__driver__.lua 3573d2502f0f5b458dc75a54cb976baec7057338 +++ tests/merging_adds_in_unrelated_revisions/__driver__.lua 3573d2502f0f5b458dc75a54cb976baec7057338 @@ -0,0 +1,25 @@ + +mtn_setup() + +-- This test relies on file-suturing +-- AT_XFAIL_IF(true) + +addfile("testfile1", "This is test file 1\n") +commit() +left = base_revision() + +remove_recursive("_MTN") +check(cmd(mtn("setup", "--branch=testbranch", ".")), 0, false, false) + +addfile("testfile2", "This is test file 2\n") +commit() +right = base_revision() + +xfail_if(true, cmd(mtn("--branch=testbranch", "merge")), 0, false, false) +check(cmd(mtn("update")), 0, false, false) + +writefile("expected_data1", "This is test file 1\n") +writefile("expected_data2", "This is test file 2\n") + +check(samefile("testfile1", "expected_data1")) +check(samefile("testfile2", "expected_data2")) ============================================================ --- tests/merging_data_in_unrelated_files/__driver__.lua 2675e2d9795e4a10c53ef95c13b23b5040815ac2 +++ tests/merging_data_in_unrelated_files/__driver__.lua 2675e2d9795e4a10c53ef95c13b23b5040815ac2 @@ -0,0 +1,28 @@ + +mtn_setup() + +-- This test relies on file-suturing + +addfile("foo", "irrelevant file\n") +commit() +anc = base_revision() + +getfile("left", "testfile") +check(cmd(mtn("add", "testfile")), 0, false, false) +commit() +left = base_revision() + +revert_to(anc) + +getfile("right", "testfile") +check(cmd(mtn("add", "testfile")), 0, false, false) +commit() +right = base_revision() + +xfail_if(true, cmd(mtn("--branch=testbranch", "merge")), 0, false, false) +check(cmd(mtn("update")), 0, false, false) + +writefile("expected_foo", "irrelevant file\n") + +check(samefile("foo", "expected_foo")) +check(samefile("left", "testfile") or samefile("right", "testfile")) ============================================================ --- tests/merging_data_in_unrelated_files/left 003d966645cd3b96511ccd2c394dc01998da180c +++ tests/merging_data_in_unrelated_files/left 003d966645cd3b96511ccd2c394dc01998da180c @@ -0,0 +1,4 @@ +line 1: foo +line 2: something unexpected +line 3: baz +line 4: quux ============================================================ --- tests/merging_data_in_unrelated_files/right 289d77f035783224b0356a05f749ba0a3b0a3246 +++ tests/merging_data_in_unrelated_files/right 289d77f035783224b0356a05f749ba0a3b0a3246 @@ -0,0 +1,4 @@ +line 1: foo +line 2: bar +line 3: baz +line 4: something unexpected ============================================================ --- tests/merging_data_in_unrelated_revisions/__driver__.lua 162fb9899df051fba839e65d2066fa927fe544db +++ tests/merging_data_in_unrelated_revisions/__driver__.lua 162fb9899df051fba839e65d2066fa927fe544db @@ -0,0 +1,22 @@ + +mtn_setup() + +-- This test relies on file-suturing + +getfile("left", "testfile") +addfile("testfile") +commit() +left = base_revision() + +remove_recursive("_MTN") +check(cmd(mtn("setup", "--branch=testbranch", ".")), 0, false, false) + +getfile("right", "testfile") +addfile("testfile") +commit() +right = base_revision() + +xfail_if(true, cmd(mtn("--branch=testbranch", "merge")), 0, false, false) +AT_CHECK(cmd(mtn("update")), 0, false, false) + +check(samefile("left", "testfile") or samefile("right", "testfile")) ============================================================ --- tests/merging_data_in_unrelated_revisions/left 003d966645cd3b96511ccd2c394dc01998da180c +++ tests/merging_data_in_unrelated_revisions/left 003d966645cd3b96511ccd2c394dc01998da180c @@ -0,0 +1,4 @@ +line 1: foo +line 2: something unexpected +line 3: baz +line 4: quux ============================================================ --- tests/merging_data_in_unrelated_revisions/right 289d77f035783224b0356a05f749ba0a3b0a3246 +++ tests/merging_data_in_unrelated_revisions/right 289d77f035783224b0356a05f749ba0a3b0a3246 @@ -0,0 +1,4 @@ +line 1: foo +line 2: bar +line 3: baz +line 4: something unexpected ============================================================ --- tests/revert_file_to_base_revision/__driver__.lua 15e25506eba289749c75a219f1f337092634d79c +++ tests/revert_file_to_base_revision/__driver__.lua 15e25506eba289749c75a219f1f337092634d79c @@ -0,0 +1,130 @@ + +mtn_setup() + +addfile("testfile0", "version 0 of first test file\n") +v1 = sha1("testfile0") +commit() + +-- check reverting a single file by name + +writefile("testfile0", "squirrils monkeys dingos\n") + +check(qgrep("squirrils", "testfile0")) +check(cmd(mtn("revert", "testfile0")), 0, false, false) +check(not qgrep("squirrils", "testfile0")) +v2 = sha1("testfile0") +check(v1 == v2) + + +-- check reverting the whole tree + +writefile("testfile0", "squirrils monkeys dingos\n") +check(cmd(mtn("status")), 0, true) +check(qgrep("testfile0", "stdout")) +check(cmd(mtn("revert", ".")), 0, false, false) +check(not exists("_MTN/work")) +check(cmd(mtn("status")), 0, true) +check(not qgrep("testfile0", "stdout")) + + +-- check reverting a delete + +check(cmd(mtn("drop", "testfile0")), 0, false, false) +check(qgrep("testfile0", "_MTN/work")) +check(cmd(mtn("status")), 0, true) +check(qgrep("testfile0", "stdout")) +check(cmd(mtn("revert", ".")), 0, false, false) +check(not exists("_MTN/work")) +check(cmd(mtn("status")), 0, true) +check(not qgrep("testfile0", "stdout")) + + +-- check reverting a change and a delete + +f = io.open("testfile0", "a") +f:write("liver and maude\n") +f:close() +check(cmd(mtn("drop", "testfile0")), 0, false, false) +check(qgrep("testfile0", "_MTN/work")) +check(cmd(mtn("status")), 0, true) +check(qgrep("testfile0", "stdout")) +check(cmd(mtn("revert", "testfile0")), 0, false, false) +check(not exists("_MTN/work")) +check(cmd(mtn("status")), 0, true) +check(not qgrep("testfile0", "stdout")) +v3 = sha1("testfile0") +check(v1 == v3) + +-- check reverting an add + +addfile("testfile1", "squirrils monkeys dingos\n") +check(qgrep("testfile1", "_MTN/work")) +check(cmd(mtn("status")), 0, true) +check(qgrep("testfile1", "stdout")) +check(cmd(mtn("revert", ".")), 0, false, false) +check(not exists("_MTN/work")) +check(cmd(mtn("status")), 0, true) +check(not qgrep("testfile1", "stdout")) + +-- check reverting a directory + +mkdir("sub") +addfile("sub/testfile2", "maude\n") +check(cmd(mtn("commit", "--message=new file")), 0, false, false) +writefile("sub/testfile2", "liver\n") +check(cmd(mtn("status")), 0, true) +check(qgrep("sub", "stdout")) +check(cmd(mtn("revert", "sub")), 0, false, false) +check(cmd(mtn("status")), 0, true) +check(not qgrep("sub", "stdout")) + +-- it also shouldn't matter how we spell the subdirectory name +writefile("sub/testfile2", "liver\n") +check(cmd(mtn("status")), 0, true) +check(qgrep("sub", "stdout")) +check(cmd(mtn("revert", "sub/")), 0, false, false) +check(cmd(mtn("status")), 0, true) +check(not qgrep("sub", "stdout")) + +-- check reverting a missing file +check(cmd(mtn("revert", ".")), 0, false, false) +os.remove("testfile0") +check(cmd(mtn("status")), 1, false, false) +check(cmd(mtn("revert", "testfile0")), 0, true, false) +check(cmd(mtn("status")), 0, false, false) + +-- check reverting some changes and leaving others + +check(cmd(mtn("revert", ".")), 0, false, false) +check(cmd(mtn("status")), 0, true) + +copyfile("testfile0", "foofile0") +copyfile("sub/testfile2", "sub/foofile2") + +check(cmd(mtn("rename", "testfile0", "foofile0")), 0, false, false) +check(cmd(mtn("rename", "sub/testfile2", "sub/foofile2")), 0, false, false) + +check(cmd(mtn("status")), 0, true) +check(qgrep("foofile0", "stdout")) +check(qgrep("foofile2", "stdout")) +check(exists("_MTN/work")) + +check(cmd(mtn("revert", "sub/foofile2")), 0, true) +check(cmd(mtn("status")), 0, true) +check(qgrep("foofile0", "stdout")) +check(not qgrep("foofile2", "stdout")) +check(exists("_MTN/work")) + +check(cmd(mtn("revert", "foofile0")), 0, true) +check(cmd(mtn("status")), 0, true) +check(not qgrep("foofile0", "stdout")) +check(not qgrep("foofile2", "stdout")) +check(not exists("_MTN/work")) + +-- check that "revert" by itself just prints usage. +writefile("foofile0", "blah\n") +v1 = sha1("foofile0") +check(cmd(mtn("revert")), 2, false, false) +v2 = sha1("foofile0") +check(v1 == v2) + ============================================================ --- tester.cc 9d2f465fbe9e75040c0d37959cf23cfa7778502d +++ tester.cc eda5f5ddd204e30c47fd9930f13ba19057a73b01 @@ -118,6 +118,15 @@ } static int + exists(lua_State *L) + { + char const * name = luaL_checkstring(L, -1); + fs::path p(name, fs::native); + lua_pushboolean(L, fs::exists(p)); + return 1; + } + + static int get_source_dir(lua_State * L) { lua_pushstring(L, source_dir.native_file_string().c_str()); @@ -202,6 +211,7 @@ lua_register(st, "leave_test_dir", leave_test_dir); lua_register(st, "mkdir", make_dir); lua_register(st, "remove_recursive", remove_recursive); + lua_register(st, "exists", exists); int ret = 2; try ============================================================ --- tester.lua 0d1895da110aae38a101dd9822188c775051c56b +++ tester.lua e466aa71c0adb51e51dacd015d7d54aa003500f0 @@ -1,10 +1,12 @@ tests = {} srcdir = get_source_dir() + test_root = nil testname = nil +wanted_fail = false -errfile = nil -errline = nil +errfile = "" +errline = -1 logfile = io.open("tester.log", "w") -- combined logfile test_log = nil -- logfile for this test @@ -190,6 +192,7 @@ L("stderr:\n") log_file_contents("ts-stderr") if ok == false then + errfile,errline = getsrcline() error(result, 2) end if result ~= ret then @@ -257,6 +260,18 @@ end end +function xfail_if(chk, ...) + local res,err = pcall(check, unpack(arg)) + if res == false then + if chk then error(false, 2) else error(err, 2) end + else + if chk then + wanted_fail = true + L("UNEXPECTED SUCCESS\n") + end + end +end + function P(...) io.write(unpack(arg)) io.flush() @@ -310,16 +325,24 @@ end end if not list_only then P("Running tests...\n") end - local failed = 0 + local counts = {} + counts.success = 0 + counts.skip = 0 + counts.xfail = 0 + counts.noxfail = 0 + counts.fail = 0 + counts.total = 0 local function runtest(i, tname) testname = tname + wanted_fail = false local shortname = nil test_root, shortname = go_to_test_dir(testname) + if i < 100 then P(" ") end if i < 10 then P(" ") end P(i .. " " .. shortname) - local spacelen = 50 - string.len(shortname) + local spacelen = 46 - string.len(shortname) local spaces = string.rep(" ", 50) if spacelen > 0 then P(string.sub(spaces, 1, spacelen)) end @@ -337,23 +360,38 @@ r,e = xpcall(driver, debug.traceback) end if r then - P("ok\n") - test_log:close() - if not debugging then clean_test_dir(testname) end + if wanted_fail then + P("unexpected success\n") + test_log:close() + leave_test_dir() + counts.noxfail = counts.noxfail + 1 + else + P("ok\n") + test_log:close() + if not debugging then clean_test_dir(testname) end + counts.success = counts.success + 1 + end else if e == true then - P(string.format("skipped (line %i)", errline)) + P(string.format("skipped (line %i)\n", errline)) test_log:close() if not debugging then clean_test_dir(testname) end + counts.skip = counts.skip + 1 + elseif e == false then + P(string.format("expected failure (line %i)\n", errline)) + test_log:close() + leave_test_dir() + counts.xfail = counts.xfail + 1 else - P(string.format("FAIL (line %i)", errline)) + P(string.format("FAIL (line %i)\n", errline)) test_log:write("\n", e, "\n") - failed = failed + 1 table.insert(failed_testlogs, tlog) test_log:close() leave_test_dir() + counts.fail = counts.fail + 1 end end + counts.total = counts.total + 1 end if run_all then @@ -377,6 +415,14 @@ end end end + + P("\n") + P(string.format("Of %i tests run:\n", counts.total)) + P(string.format("\t%i succeeded\n", counts.success)) + P(string.format("\t%i failed\n", counts.fail)) + P(string.format("\t%i had expected failures\n", counts.xfail)) + P(string.format("\t%i succeeded unexpectedly\n", counts.noxfail)) + P(string.format("\t%i were skipped\n", counts.skip)) for i,log in pairs(failed_testlogs) do local tlog = io.open(log, "r") @@ -387,5 +433,9 @@ end end - if failed == 0 then return 0 else return 1 end + if counts.success + counts.skip + counts.xfail == counts.total then + return 0 + else + return 1 + end end ============================================================ --- tests/test_hooks.lua 234fbb463da7e4614f0b0b279f21c658e74d3706 +++ tests/test_hooks.lua ce6008e93ec7c8931517ff0173925418e4c6dd6f @@ -50,6 +50,7 @@ end function ignore_file(name) + if (string.find(name, "ts-std", 1, true)) then return true end if (string.find(name, "testsuite.log")) then return true end if (string.find(name, "test_hooks.lua")) then return true end if (string.find(name, "keys")) then return true end ============================================================ --- testsuite.at 33ef9f80a877e665e9ac8171fb29ad5475178285 +++ testsuite.at d87e4f1992dc6a6a58b5575317b401748758ee6e @@ -556,16 +556,16 @@ m4_include(tests/t_fork.at)# m4_include(tests/t_update.at)# m4_include(tests/t_merge.at)# -m4_include(tests/t_merge_add.at) -m4_include(tests/t_related_merge2_data.at) -m4_include(tests/t_merge2_add.at) -m4_include(tests/t_merge2_data.at) -m4_include(tests/t_unidiff2.at) -m4_include(tests/t_cwork.at) -m4_include(tests/t_revert.at) -m4_include(tests/t_add.at) -m4_include(tests/t_drop.at) -m4_include(tests/t_drop_missing.at) +m4_include(tests/t_merge_add.at)# +m4_include(tests/t_related_merge2_data.at)# +m4_include(tests/t_merge2_add.at)# +m4_include(tests/t_merge2_data.at)# +m4_include(tests/t_unidiff2.at)# +m4_include(tests/t_cwork.at)# +m4_include(tests/t_revert.at)# +m4_include(tests/t_add.at)# +m4_include(tests/t_drop.at)# +m4_include(tests/t_drop_missing.at)# m4_include(tests/t_cross.at) m4_include(tests/t_rename.at) m4_include(tests/t_renamed.at) ============================================================ --- testsuite.lua c04000a84ded45f0920c2fee726b5ac6f4831ab4 +++ testsuite.lua 65bc566aa260fd09e30fbfe41566adfb434d9ea1 @@ -63,10 +63,23 @@ return cmd(grep("-q", what, where))() == 0 end -function addfile(filename) +function addfile(filename, contents) + if contents ~= nil then writefile(filename, contents) end check(cmd(mtn("add", filename)), 0, false, false) end +function revert_to(rev, branch) + remove_recursive("_MTN.old") + os.rename("_MTN", "_MTN.old") + + if branch == nil then + check(cmd(mtn("checkout", "--revision", rev, ".")), 0, false) + else + check(cmd(mtn("checkout", "--branch", branch, "--revision", rev, ".")), 0, false) + end + check(base_revision() == rev) +end + function canonicalize(filename) local ostype = os.getenv("OSTYPE") local osenv = os.getenv("OS") @@ -96,3 +109,13 @@ table.insert(tests, "tests/creating_a_fork") table.insert(tests, "tests/creating_a_fork_and_updating") table.insert(tests, "tests/creating_a_fork_and_merging") +table.insert(tests, "tests/merging_adds") +table.insert(tests, "tests/merging_data_in_unrelated_files") +table.insert(tests, "tests/merging_adds_in_unrelated_revisions") +table.insert(tests, "tests/merging_data_in_unrelated_revisions") +table.insert(tests, "tests/calculation_of_incorrect_unidiffs") +table.insert(tests, "tests/delete_work_file_on_checkout") +table.insert(tests, "tests/revert_file_to_base_revision") +table.insert(tests, "tests/addition_of_files_and_directories") +table.insert(tests, "tests/add_and_then_drop_file_does_nothing") +table.insert(tests, "tests/drop_missing_and_unknown_files")