This is the RSE patchset for the Monotone VCS. The patchset is entirely enclosed in... #if defined(RSE) /* */ [...] #endif ...and this way can be clearly distinguished. The corresponds the following major changes it includes: o environment-variables: This adds the two environment variables MTN_DBFILE and MTN_KEYDIR as defaults for the (bootstrapping) mtn(1) command line options --db and --keydir. This allows you to provide values for them without having to pass the options all the time. o alt-book-keeping-root: This allows mtn(1) to accept an alternative book-keeping directory. The default still is "_MTN", but alternatively one can rename this to ".mtn". If one sets the environment variable "MTN_BKROOT" one can change ".mtn" to an arbitrary sub-directory name and additionally even force its use on workspace creation operations. o cosmetics-netsync: This is just a small cosmetics change. It reduces the annoying "doing anonymous pull; use -kKEYNAME if you need authentication" to just "doing anonymous pull" as mtn(1) doesn't have to teach people. That's for what the documentation is for. o cosmetics-stat: The workspace top-level directory is shown as "" instead of nothing. o cosmetics-diff-and-log: This cosmetically "improves" the output of "mtn diff" and "mtn log". For "mtn diff" The output of "mtn diff" now uses a separator line consisting of 67 (instead of 60) "=" characters to align with cvs(1)'s well known output. Additionally, two new command line options for "mtn diff" allow one to disable some outputs: "--no-show-header" disables the output of the redundant "#..." header lines at the top of the output and "--no-show-separator" disables the output of the separator line at all. The output of "mtn log" is improved by aligning the single-line certificate values and by indenting the file change information by just 4 instead of 8 characters. o diff-index: This adds "Index:" lines to the output of "mtn diff" in order to align with the "svn diff" and "cvs diff" outputs. This also helps patch(1) to clearly identify the file to patch. o extra-commands: This adds "mtn fuse", "mtn conflicts", "mtn revision" and "mtn base" commands. They are all simple but convenient Lua wrappers. o dot-mtn-message: Support a ".mtn-message" file in the root-directory as a template for the commit messages. Ralf S. Engelschall rse@engelschall.com www.engelschall.com =================================================================== Index: cmd_diff_log.cc --- cmd_diff_log.cc fd455eacd212d63aa201035e9417b9cf726f498b +++ cmd_diff_log.cc 234ce11a43478c6ed8fb9b711f31039e3609bd60 @@ -100,6 +100,27 @@ print_indented_set(ostream & os, set const & s, size_t max_cols) { +#if defined(RSE) /* cosmetics-diff-and-log */ + size_t cols = 4; + os << " "; + for (set::const_iterator i = s.begin(); + i != s.end(); i++) + { + const string str = lexical_cast(*i); + if (cols > 4 && cols + str.size() + 1 >= max_cols) + { + cols = 4; + os << "\n "; + } + else if (cols > 4) { + os << ' '; + cols += 1; + } + os << str; + cols += str.size(); + } + os << '\n'; +#else size_t cols = 8; os << " "; for (set::const_iterator i = s.begin(); @@ -117,6 +138,7 @@ print_indented_set(ostream & os, cols += str.size() + 1; } os << '\n'; +#endif } void @@ -135,7 +157,11 @@ changes_summary::print(ostream & os, siz for (map::const_iterator i = cs.nodes_renamed.begin(); i != cs.nodes_renamed.end(); i++) +#if defined(RSE) /* cosmetics-diff-and-log */ + os << " " << i->first +#else os << " " << i->first +#endif << " to " << i->second << '\n'; } @@ -228,7 +254,12 @@ static void } static void +#if defined(RSE) /* cosmetics-diff-and-log */ +dump_diffs(app_state & app, + lua_hooks & lua, +#else dump_diffs(lua_hooks & lua, +#endif database & db, cset const & cs, set const & paths, @@ -238,8 +269,13 @@ dump_diffs(lua_hooks & lua, bool show_encloser, bool limit_paths = false) { +#if defined(RSE) /* cosmetics-diff-and-log */ + // 67 is somewhat arbitrary (CVS uses this), but less than 80 + string patch_sep = string(67, '='); +#else // 60 is somewhat arbitrary, but less than 80 string patch_sep = string(60, '='); +#endif for (map::const_iterator i = cs.files_added.begin(); @@ -248,6 +284,9 @@ dump_diffs(lua_hooks & lua, if (limit_paths && paths.find(i->first) == paths.end()) continue; +#if defined(RSE) /* cosmetics-diff-and-log */ + if (!app.opts.no_show_separator) +#endif output << patch_sep << '\n'; data unpacked; vector lines; @@ -294,6 +333,9 @@ dump_diffs(lua_hooks & lua, file_data f_old; data data_old, data_new; +#if defined(RSE) /* cosmetics-diff-and-log */ + if (!app.opts.no_show_separator) +#endif output << patch_sep << '\n'; db.get_file_version(delta_entry_src(i), f_old); @@ -331,7 +373,12 @@ static void } static void +#if defined(RSE) /* cosmetics-diff-and-log */ +dump_diffs(app_state & app, + lua_hooks & lua, +#else dump_diffs(lua_hooks & lua, +#endif database & db, cset const & cs, std::ostream & output, @@ -340,8 +387,13 @@ dump_diffs(lua_hooks & lua, bool show_encloser) { set dummy; +#if defined(RSE) /* cosmetics-diff-and-log */ + dump_diffs(app, lua, db, cs, dummy, output, + diff_format, new_is_archived, show_encloser); +#else dump_diffs(lua, db, cs, dummy, output, diff_format, new_is_archived, show_encloser); +#endif } // common functionality for diff and automate content_diff to determine @@ -506,6 +558,9 @@ CMD(diff, "diff", "di", CMD_REF(informat data summary; write_cset(included, summary); +#if defined(RSE) /* cosmetics-diff-and-log */ + if (!app.opts.no_show_header) { +#endif vector lines; split_into_lines(summary(), lines); cout << "#\n"; @@ -521,6 +576,9 @@ CMD(diff, "diff", "di", CMD_REF(informat cout << "# " << _("no changes") << '\n'; } cout << "#\n"; +#if defined(RSE) /* cosmetics-diff-and-log */ + } +#endif if (app.opts.diff_format == external_diff) { @@ -528,9 +586,15 @@ CMD(diff, "diff", "di", CMD_REF(informat } else { +#if defined(RSE) /* cosmetics-diff-and-log */ + dump_diffs(app, app.lua, db, included, cout, + app.opts.diff_format, new_is_archived, + !app.opts.no_show_encloser); +#else dump_diffs(app.lua, db, included, cout, app.opts.diff_format, new_is_archived, !app.opts.no_show_encloser); +#endif } } @@ -558,8 +622,13 @@ CMD_AUTOMATE(content_diff, N_("[FILE [.. prepare_diff(app, db, included, args, new_is_archived, dummy_header); +#if defined(RSE) /* cosmetics-diff-and-log */ + dump_diffs(app, app.lua, db, included, output, + app.opts.diff_format, new_is_archived, !app.opts.no_show_encloser); +#else dump_diffs(app.lua, db, included, output, app.opts.diff_format, new_is_archived, !app.opts.no_show_encloser); +#endif } @@ -889,7 +958,11 @@ CMD(log, "log", "", CMD_REF(informative) else { out << string(65, '-') << '\n'; +#if defined(RSE) /* cosmetics-diff-and-log */ + out << "Revision: " << rid << '\n'; +#else out << "Revision: " << rid << '\n'; +#endif changes_summary csum; @@ -904,12 +977,21 @@ CMD(log, "log", "", CMD_REF(informative) for (set::const_iterator anc = ancestors.begin(); anc != ancestors.end(); ++anc) +#if defined(RSE) /* cosmetics-diff-and-log */ + out << "Ancestor: " << *anc << '\n'; + + log_certs(project, out, rid, author_name, "Author: ", false); + log_certs(project, out, rid, date_name, "Date: ", false); + log_certs(project, out, rid, branch_name, "Branch: ", false); + log_certs(project, out, rid, tag_name, "Tag: ", false); +#else out << "Ancestor: " << *anc << '\n'; log_certs(project, out, rid, author_name, "Author: ", false); log_certs(project, out, rid, date_name, "Date: ", false); log_certs(project, out, rid, branch_name, "Branch: ", false); log_certs(project, out, rid, tag_name, "Tag: ", false); +#endif if (!app.opts.no_files && !csum.cs.empty()) { @@ -919,16 +1001,26 @@ CMD(log, "log", "", CMD_REF(informative) } log_certs(project, out, rid, changelog_name, "ChangeLog: ", true); +#if defined(RSE) /* cosmetics-diff-and-log */ + log_certs(project, out, rid, comment_name, "Comments: ", true); +#else log_certs(project, out, rid, comment_name, "Comments: ", true); +#endif } if (app.opts.diffs) { for (edge_map::const_iterator e = rev.edges.begin(); e != rev.edges.end(); ++e) +#if defined(RSE) /* cosmetics-diff-and-log */ + dump_diffs(app, app.lua, db, edge_changes(e), diff_paths, out, + app.opts.diff_format, true, + !app.opts.no_show_encloser, !mask.empty()); +#else dump_diffs(app.lua, db, edge_changes(e), diff_paths, out, app.opts.diff_format, true, !app.opts.no_show_encloser, !mask.empty()); +#endif } if (next > 0) =================================================================== Index: cmd_netsync.cc --- cmd_netsync.cc 34174bb7ce83722d7a9859b7065c40243ca0ba75 +++ cmd_netsync.cc 9e4cf97f7d4f031cd68999517822f5fc6121fb82 @@ -253,7 +253,11 @@ CMD(pull, "pull", "", CMD_REF(network), args, info, false); if (app.opts.signing_key() == "") +#if defined(RSE) /* cosmetics-netsync */ + P(F("doing anonymous pull")); +#else P(F("doing anonymous pull; use -kKEYNAME if you need authentication")); +#endif run_netsync_protocol(app.opts, app.lua, project, keys, client_voice, sink_role, info); @@ -356,9 +360,16 @@ CMD(clone, "clone", "", CMD_REF(network) // paths.cc's idea of the current workspace root is wrong at this point if (internal_db) +#if defined(RSE) /* alt-book-keeping-root */ + app.opts.dbname = system_path((directory_exists(workspace_dir / alt_bookkeeping_root_component) ? + (workspace_dir / alt_bookkeeping_root_component) : + (workspace_dir / bookkeeping_root_component)) + / ws_internal_db_file_name); +#else app.opts.dbname = system_path(workspace_dir / bookkeeping_root_component / ws_internal_db_file_name); +#endif // this is actually stupid, but app.opts.branchname must be set here // otherwise it will not be written into _MTN/options, in case @@ -383,7 +394,11 @@ CMD(clone, "clone", "", CMD_REF(network) info, true, true, false); if (app.opts.signing_key() == "") +#if defined(RSE) /* cosmetics-netsync */ + P(F("doing anonymous pull")); +#else P(F("doing anonymous pull; use -kKEYNAME if you need authentication")); +#endif // make sure we're back in the original dir so that file: URIs work change_current_working_dir(start_dir); =================================================================== Index: cmd_ws_commit.cc --- cmd_ws_commit.cc 77b18e38145f345b0eb65b73cc05619464a8fc59 +++ cmd_ws_commit.cc da1133ed41a571f4921798526af49743dc48d0a2 @@ -73,7 +73,14 @@ revision_summary(revision_t const & rev, for (set::const_iterator i = cs.dirs_added.begin(); i != cs.dirs_added.end(); ++i) +#if defined(RSE) /* cosmetics-stat */ + if ((*i) == file_path()) + out += (F(" added \"\"")).str() += '\n'; + else + out += (F(" added %s") % *i).str() += '\n'; +#else out += (F(" added %s") % *i).str() += '\n'; +#endif for (map::const_iterator i = cs.files_added.begin(); i != cs.files_added.end(); ++i) @@ -1451,12 +1458,20 @@ CMD_NO_WORKSPACE(import, "import", "", C catch (...) { // clean up before rethrowing +#if defined(RSE) /* alt-book-keeping-root */ + delete_dir_recursive(directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root); +#else delete_dir_recursive(bookkeeping_root); +#endif throw; } // clean up +#if defined(RSE) /* alt-book-keeping-root */ + delete_dir_recursive(directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root); +#else delete_dir_recursive(bookkeeping_root); +#endif } CMD_NO_WORKSPACE(migrate_workspace, "migrate_workspace", "", CMD_REF(tree), =================================================================== Index: diff_patch.cc --- diff_patch.cc 211424b28fdc3a5dcdae5a781feea97e1e915326 +++ diff_patch.cc 0ebf55f44a911f3e72dc381c1dda3e762103ae7e @@ -1449,6 +1449,9 @@ make_diff(string const & filename1, { case unified_diff: { +#if defined(RSE) /* diff-index */ + ost << "Index: " << filename2 << '\n'; +#endif ost << "--- " << filename1 << '\t' << id1 << '\n'; ost << "+++ " << filename2 << '\t' @@ -1460,6 +1463,9 @@ make_diff(string const & filename1, } case context_diff: { +#if defined(RSE) /* diff-index */ + ost << "Index: " << filename2 << '\n'; +#endif ost << "*** " << filename1 << '\t' << id1 << '\n'; ost << "--- " << filename2 << '\t' =================================================================== Index: file_io.cc --- file_io.cc e18d07679c2d210a40f18b67ed0b4b96f5a2c21a +++ file_io.cc 291d078c4c156479590b4d553810b6a1dff161a3 @@ -397,16 +397,36 @@ write_data(file_path const & path, data write_data(file_path const & path, data const & dat) { // use the bookkeeping root as the temporary directory. +#if defined(RSE) /* alt-book-keeping-root */ + if (directory_exists(alt_bookkeeping_root)) { + assert_path_is_directory(alt_bookkeeping_root); + write_data_impl(path, dat, alt_bookkeeping_root, false); + } + else { +#endif assert_path_is_directory(bookkeeping_root); write_data_impl(path, dat, bookkeeping_root, false); +#if defined(RSE) /* alt-book-keeping-root */ + } +#endif } void write_data(bookkeeping_path const & path, data const & dat) { // use the bookkeeping root as the temporary directory. +#if defined(RSE) /* alt-book-keeping-root */ + if (directory_exists(alt_bookkeeping_root)) { + assert_path_is_directory(alt_bookkeeping_root); + write_data_impl(path, dat, alt_bookkeeping_root, false); + } + else { +#endif assert_path_is_directory(bookkeeping_root); write_data_impl(path, dat, bookkeeping_root, false); +#if defined(RSE) /* alt-book-keeping-root */ + } +#endif } void =================================================================== Index: lua_hooks.cc --- lua_hooks.cc e93b511c04660956edd432fda230877ca3c6fc2b +++ lua_hooks.cc 0286c7452a45384187df02ccf6e33ee311828372 @@ -209,7 +209,11 @@ lua_hooks::load_rcfiles(options & opts) { load_rcfile(opts.conf_dir / "monotonerc", false); } +#if defined(RSE) /* alt-book-keeping-root */ + load_rcfile((directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / "monotonerc", false); +#else load_rcfile(bookkeeping_root / "monotonerc", false); +#endif } // Command-line rcfiles override even that. =================================================================== Index: options_list.hh --- options_list.hh cd0ff5a0d051b4644c02a5f81ed262a512c65360 +++ options_list.hh 25e39a62d57d8c82427a8deae709826cc2bff4a3 @@ -233,6 +233,24 @@ OPTION(diff_options, no_show_encloser, f no_show_encloser = true; } #endif +#if defined(RSE) /* cosmetics-diff-and-log */ +OPTVAR(diff_options, bool, no_show_header, false) +OPTION(diff_options, no_show_header, false, "no-show-header", + gettext_noop("do not show the summary header")) +#ifdef option_bodies +{ + no_show_header = true; +} +#endif +OPTVAR(diff_options, bool, no_show_separator, false) +OPTION(diff_options, no_show_separator, false, "no-show-separator", + gettext_noop("do not show the separator line")) +#ifdef option_bodies +{ + no_show_separator = true; +} +#endif +#endif OPT(diffs, "diffs", bool, false, gettext_noop("print diffs along with logs")) #ifdef option_bodies =================================================================== Index: paths.cc --- paths.cc dde6659e387c8890c96278ea29e3196bdf588139 +++ paths.cc 3101fad7134c9f2383bee0b65219cdec1d46f2e4 @@ -227,7 +227,11 @@ in_bookkeeping_dir(string const & path) static inline bool in_bookkeeping_dir(string const & path) { +#if defined(RSE) /* alt-book-keeping-root */ + if (path.size() == 0 || (path[0] != '_' && path[0] != '.')) +#else if (path.size() == 0 || (path[0] != '_')) +#endif return false; if (path.size() == 1 || (path[1] != 'M' && path[1] != 'm')) return false; @@ -929,6 +933,9 @@ find_and_go_to_workspace(string const & // first look for the current name of the bookkeeping directory. // if we don't find it, look for it under the old name, so that // migration has a chance to work. +#if defined(RSE) /* alt-book-keeping-root */ + if (!find_bookdir(root, alt_bookkeeping_root_component, current, removed)) +#endif if (!find_bookdir(root, bookkeeping_root_component, current, removed)) if (!find_bookdir(root, old_bookkeeping_root_component, current, removed)) return false; @@ -1488,7 +1495,11 @@ static void check_bk_normalizes_to(char static void check_bk_normalizes_to(char const * before, char const * after) { +#if defined(RSE) /* alt-book-keeping-root */ + bookkeeping_path bp((directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / before); +#else bookkeeping_path bp(bookkeeping_root / before); +#endif L(FL("normalizing %s to %s (got %s)") % before % after % bp); UNIT_TEST_CHECK(bp.as_external() == after); UNIT_TEST_CHECK(bookkeeping_path(bp.as_internal()).as_internal() == bp.as_internal()); =================================================================== Index: paths.hh --- paths.hh 7dd47155b2962d3e4f5fe8764b11c955791803fc +++ paths.hh d29c84938079b1571cc843c7b7c6c61e420421d7 @@ -328,6 +328,15 @@ private: // for migration #define old_bookkeeping_root_component (path_component("MT")) +#if defined(RSE) /* alt-book-keeping-root */ +#ifndef MTN_ALT_BKROOT +#define MTN_ALT_BKROOT ".mtn" +#endif +#define MTN_ALT_BKROOT_ARG (getenv("MTN_BKROOT") != NULL ? getenv("MTN_BKROOT") : MTN_ALT_BKROOT) +#define alt_bookkeeping_root (bookkeeping_path(MTN_ALT_BKROOT_ARG)) +#define alt_bookkeeping_root_component (path_component(MTN_ALT_BKROOT_ARG)) +#endif + // this will always be an absolute path class system_path : public any_path { =================================================================== Index: roster_merge.cc --- roster_merge.cc 9bb39c2c87f92180b0a5db5697ff6ea15b82d25b +++ roster_merge.cc 9b75d448ee88458f86092ee8d7f4720b24d211a9 @@ -1854,6 +1854,20 @@ roster_merge(roster_t const & left_paren result.roster.detach_node(n->self); result.invalid_name_conflicts.push_back(conflict); } +#if defined(RSE) /* alt-book-keeping-root */ + if (result_root->has_child(alt_bookkeeping_root_component)) + { + invalid_name_conflict conflict; + node_t n = result_root->get_child(alt_bookkeeping_root_component); + conflict.nid = n->self; + conflict.parent_name.first = n->parent; + conflict.parent_name.second = n->name; + I(n->name == alt_bookkeeping_root_component); + + result.roster.detach_node(n->self); + result.invalid_name_conflicts.push_back(conflict); + } +#endif } } @@ -2786,7 +2800,12 @@ struct simple_invalid_name_conflict : pu I(!result.is_clean()); invalid_name_conflict const & c = idx(result.invalid_name_conflicts, 0); I(c.nid == bad_dir_nid); +#if defined(RSE) /* alt-book-keeping-root */ + I( c.parent_name == make_pair(new_root_nid, bookkeeping_root_component) + || c.parent_name == make_pair(new_root_nid, alt_bookkeeping_root_component)); +#else I(c.parent_name == make_pair(new_root_nid, bookkeeping_root_component)); +#endif // this tests it was detached, implicitly result.roster.attach_node(bad_dir_nid, file_path_internal("dir_formerly_known_as__MTN")); result.invalid_name_conflicts.pop_back(); =================================================================== Index: std_hooks.lua --- std_hooks.lua 7e22137903335a8b7dd2ce7bc9969b8962ff85be +++ std_hooks.lua 751f8ca2cb91b2ecca05caef00466bd020ce3ca8 @@ -284,6 +284,15 @@ function edit_comment(basetext, user_log if user_log_message == "" or string.sub(user_log_message, -1) ~= "\n" then tmp:write("\n") end + -- #if defined(RSE) /* dot-mtn-message */ + local template_log_message = read_contents_of_file(".mtn-message", "r") + if template_log_message ~= nil then + tmp:write(template_log_message) + if template_log_message == "" or string.sub(template_log_message, -1) ~= "\n" then + tmp:write("\n") + end + end + -- #endif tmp:write(basetext) io.close(tmp) @@ -1273,3 +1282,201 @@ end return push_hook_functions(notifier) end end + +-- #if defined(RSE) /* extra-command */ + +-- extra command: "mtn fuse REVISION" +register_command( + "fuse", "REVISION", + "Fuse revision into workspace with conflict markers.", + "Fuse the specified revision into the current workspace by merging " .. + "the revision into the workspace while providing inline conflict " .. + "markers for manually resolving the conflicts in the workspace " .. + "before comitting the conflicts-resolved workspace as the new " .. + "merged revision.", + "command_fuse" +) +function command_fuse(revision) + if revision == nil then + io.stderr:write("mtn: fuse: ERROR: revision not given\n") + return + end + if program_exists_in_path("mtn") == 0 then + io.stderr:write("mtn: fuse: ERROR: require Monotone command \"mtn\" in PATH\n") + return + end + mtn_automate("get_option", "branch") -- make sure we have a valid workspace + local cmd = + "MTN_MERGE=diffutils " .. + "MTN_MERGE_DIFFUTILS=\"partial,diff3opts=-E\" " .. + "mtn " .. "merge_into_workspace " .. revision + local rc = execute("sh", "-c", cmd) + if rc ~= 0 then + io.stderr:write("mtn: fuse: ERROR: failed to execute command \"" .. cmd .. "\"\n") + end +end + +-- extra command: "mtn conflicts" +register_command( + "conflicts", "", + "Lists files in workspace containing conflict markers.", + "Lists all files in the current workspace containing the " .. + "conflict markers produced by GNU diffutils' \"diff3\" " .. + "command. This indicates still unresolved merge conflicts.", + "command_conflicts" +) +function command_conflicts() + if program_exists_in_path("egrep") == 0 then + io.stderr:write("mtn: conflicts: ERROR: require GNU grep command \"egrep\" in PATH\n") + return + end + mtn_automate("get_option", "branch") -- make sure we have a valid workspace + local rc = execute( + "egrep", + "--files-with-matches", + "--recursive", + "^(<<<<<<<|=======|>>>>>>>) ", + "." + ) +end + +-- extra command: "mtn rev[ision] REVISION [ANCESTOR-REVISION]" +register_command( + "revision", "REVISION [ANCESTOR-REVISION]", + "Shows summary information about revision(s)", + "Shows summary information about a particular revision " .. + "(or a range of revisions in case an ancestor revision is also specified). " .. + "This is just a convenience wrapper command around \"mtn log --diffs\".", + "command_revision" +) +alias_command( + "revision", + "rev" +) +function command_revision(revision, ancestor) + if revision == nil then + io.stderr:write("mtn: revision: ERROR: no revision specified\n") + return + end + if ancestor == nil then + ancestor = revision + end + mtn_automate("get_option", "branch") -- make sure we have a valid workspace + execute("mtn", "log", "--diffs", "--no-graph", "--from", ancestor, "--to", revision) + if rc ~= 0 then + io.stderr:write("mtn: revision: ERROR: failed to execute\n") + end +end + +-- extra command: "mtn base {upgrade|diff}" +register_command( + "base", "upgrade|diff", + "Upgrades or compares current branch against base branch", + "Upgrade current branch from base branch or compares current " .. + "branch against base branch. The base branch has to be stored " .. + "either in the \"mtn:base\" attribute of the root directory " .. + "or in a \".mtn-base\" file in the root directory.", + "command_base" +) +function command_base(op) + -- sanity check command line + if op == nil then + io.stderr:write("mtn: base: ERROR: no operation specified\n") + return + end + if op ~= "upgrade" and op ~= "diff" then + io.stderr:write("mtn: base: ERROR: either \"upgrade\" or \"diff\" operation has to be specified\n") + return + end + + -- determine current branch of workspace + local branch_this = nil + local rc, txt = mtn_automate("get_option", "branch") + if txt ~= nil then + branch_this = string.match(txt, "^%s*(%S+)%s*$") + end + if branch_this == nil then + io.stderr:write("mtn: base: ERROR: failed to determine current branch\n") + return + end + + -- determine base branch of workspace + local branch_base = nil + local rc, txt = mtn_automate("get_attributes", ".") + if txt ~= nil then + branch_base = string.match(txt, "attr%s+\"mtn:base\"%s+\"([^\"]+)\"") + end + if branch_base == nil then + local txt = read_contents_of_file(".mtn-base", "r") + if txt ~= nil then + branch_base = string.match(txt, "^%s*(%S+)%s*$") + end + end + if branch_base == nil then + io.stderr:write("mtn: base: ERROR: failed to determine base branch\n") + return + end + + -- dispatch according to operation + if op == "upgrade" then + -- upgrade current branch by merging in revisions of base branch + local rc = execute("mtn", "propagate", branch_base, branch_this) + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn propagate\"\n") + return + end + rc = execute("mtn", "update") + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn update\"\n") + return + end + elseif op == "diff" then + -- upgrade current branch by merging in revisions of base branch + local rc = execute("mtn", "diff", "-r", "h:" .. branch_base, "-r", "h:" .. branch_this) + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn diff\"\n") + return + end + end + return +end + +-- extra command: "mtn init" +register_command( + "init", "BRANCH", + "Place local directory under local version control.", + "Creates a new _MTN/mtn.db database and places the local " .. + "directory tree under version control using this database.", + "command_init" +) +function command_init(branch) + -- sanity check command line + if branch == nil then + io.stderr:write("mtn: init: ERROR: no branch specified\n") + return + end + + -- create new database + execute("mtn", "--db=mtn.db", "db", "init") + + -- place current directory under version control + execute("mtn", "--db=mtn.db", "setup", "-b", branch) + + -- use alternative book-keeping directory name + execute("mv", "_MTN", ".mtn") + + -- place database into book-keeping directory + execute("mv", "mtn.db", ".mtn/mtn.db") + local txt = read_contents_of_file(".mtn/options") + txt = string.gsub(txt, "database \"[^\"]*\"", "database \".mtn/mtn.db\"") + options = io.open(".mtn/options", "w") + options:write(txt) + io.close(options) + + -- perform a simple operation so that Monotone + -- updates the book-keeping directory + execute("sh", "-c", "mtn stat >/dev/null 2>&1") +end + +-- #endif + =================================================================== Index: work.cc --- work.cc 0ef66673796d2a408587d70530944a5a8747d488 +++ work.cc 8cfa90878704a6fc1553c4dc14bf1be428831674 @@ -57,35 +57,55 @@ get_revision_path(bookkeeping_path & m_p static void get_revision_path(bookkeeping_path & m_path) { +#if defined(RSE) /* alt-book-keeping-root */ + m_path = (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / revision_file_name; +#else m_path = bookkeeping_root / revision_file_name; +#endif L(FL("revision path is %s") % m_path); } static void get_options_path(bookkeeping_path & o_path) { +#if defined(RSE) /* alt-book-keeping-root */ + o_path = (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / options_file_name; +#else o_path = bookkeeping_root / options_file_name; +#endif L(FL("options path is %s") % o_path); } static void get_options_path(system_path const & workspace, system_path & o_path) { +#if defined(RSE) /* alt-book-keeping-root */ + o_path = (directory_exists(workspace / alt_bookkeeping_root_component) ? (workspace / alt_bookkeeping_root_component) : (workspace / bookkeeping_root_component)) / options_file_name; +#else o_path = workspace / bookkeeping_root_component / options_file_name; +#endif L(FL("options path is %s") % o_path); } static void get_inodeprints_path(bookkeeping_path & ip_path) { +#if defined(RSE) /* alt-book-keeping-root */ + ip_path = (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / inodeprints_file_name; +#else ip_path = bookkeeping_root / inodeprints_file_name; +#endif L(FL("inodeprints path is %s") % ip_path); } static void get_user_log_path(bookkeeping_path & ul_path) { +#if defined(RSE) /* alt-book-keeping-root */ + ul_path = (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / user_log_file_name; +#else ul_path = bookkeeping_root / user_log_file_name; +#endif L(FL("user log path is %s") % ul_path); } @@ -96,7 +116,11 @@ directory_is_workspace(system_path const { // as far as the users of this function are concerned, a version 0 // workspace (MT directory instead of _MTN) does not count. +#if defined(RSE) /* alt-book-keeping-root */ + return (directory_exists(dir / alt_bookkeeping_root_component) || directory_exists(dir / bookkeeping_root_component)); +#else return directory_exists(dir / bookkeeping_root_component); +#endif } bool workspace::found; @@ -129,13 +153,24 @@ workspace::create_workspace(options cons go_to_workspace(new_dir); mark_std_paths_used(); +#if defined(RSE) /* alt-book-keeping-root */ + N(!(directory_exists(bookkeeping_root) || directory_exists(alt_bookkeeping_root)), + F("monotone bookkeeping directory '%s' or '%s' already exists in '%s'") + % bookkeeping_root % alt_bookkeeping_root % new_dir); +#else N(!directory_exists(bookkeeping_root), F("monotone bookkeeping directory '%s' already exists in '%s'") % bookkeeping_root % new_dir); +#endif L(FL("creating bookkeeping directory '%s' for workspace in '%s'") % bookkeeping_root % new_dir); +#if defined(RSE) /* alt-book-keeping-root */ + if (getenv("MTN_BKROOT") != NULL) + mkdir_p(bookkeeping_path(getenv("MTN_BKROOT"))); + else +#endif mkdir_p(bookkeeping_root); workspace::found = true; @@ -483,9 +518,16 @@ workspace::get_database_option(system_pa rsa_keypair_id key_option; system_path keydir_option; +#if defined(RSE) /* alt-book-keeping-root */ + system_path o_path = (( directory_exists(workspace / alt_bookkeeping_root_component) + ? (workspace / alt_bookkeeping_root_component) + : (workspace / bookkeeping_root_component)) + / options_file_name); +#else system_path o_path = (workspace / bookkeeping_root_component / options_file_name); +#endif read_options_file(o_path, database_option, branch_option, key_option, keydir_option); } @@ -563,7 +605,11 @@ workspace::get_local_dump_path(bookkeepi { N(workspace::found, F("workspace required but not found")); +#if defined(RSE) /* alt-book-keeping-root */ + d_path = (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / local_dump_file_name; +#else d_path = bookkeeping_root / local_dump_file_name; +#endif L(FL("local dump path is %s") % d_path); } @@ -976,7 +1022,11 @@ path_for_detached_nids() static inline bookkeeping_path path_for_detached_nids() { +#if defined(RSE) /* alt-book-keeping-root */ + return (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / "detached"; +#else return bookkeeping_root / "detached"; +#endif } static inline bookkeeping_path @@ -1715,9 +1765,15 @@ workspace::perform_pivot_root(database & N(is_dir_t(new_roster.get_node(new_root)), F("proposed new root directory '%s' is not a directory") % new_root); { +#if defined(RSE) /* alt-book-keeping-root */ + N(!(new_roster.has_node(new_root / bookkeeping_root_component) || new_roster.has_node(new_root / alt_bookkeeping_root_component)), + F("proposed new root directory '%s' contains illegal path %s or %s") + % new_root % bookkeeping_root % alt_bookkeeping_root); +#else N(!new_roster.has_node(new_root / bookkeeping_root_component), F("proposed new root directory '%s' contains illegal path %s") % new_root % bookkeeping_root); +#endif } { =================================================================== Index: work_migration.cc --- work_migration.cc 649da47d7b879f1f2814100fdb831b1de7ecaeca +++ work_migration.cc 21ada50dddc95ba4c9068f89e8db88409c3e2c68 @@ -57,9 +57,18 @@ get_ws_format() { unsigned int format; bookkeeping_path f_path = bookkeeping_root / "format"; +#if defined(RSE) /* alt-book-keeping-root */ + bookkeeping_path alt_f_path = alt_bookkeeping_root / "format"; + if (!file_exists(f_path) && !file_exists(alt_f_path)) +#else if (!file_exists(f_path)) +#endif { +#if defined(RSE) /* alt-book-keeping-root */ + if (directory_exists(bookkeeping_root) || directory_exists(alt_bookkeeping_root)) +#else if (directory_exists(bookkeeping_root)) +#endif format = 1; else if (directory_exists(file_path() / old_bookkeeping_root_component)) format = 0; @@ -71,7 +80,11 @@ get_ws_format() data f_dat; try { +#if defined(RSE) /* alt-book-keeping-root */ + read_data(file_exists(alt_f_path) ? alt_f_path : f_path, f_dat); +#else read_data(f_path, f_dat); +#endif format = lexical_cast(remove_ws(f_dat())); } catch (exception & e) @@ -82,7 +95,11 @@ get_ws_format() if (format == 1) { W(F("_MTN/format should not exist in a format 1 workspace; corrected")); +#if defined(RSE) /* alt-book-keeping-root */ + delete_file(file_exists(alt_f_path) ? alt_f_path : f_path); +#else delete_file(f_path); +#endif } } return format; @@ -92,6 +109,9 @@ workspace::write_ws_format() workspace::write_ws_format() { bookkeeping_path f_path = bookkeeping_root / "format"; +#if defined(RSE) /* alt-book-keeping-root */ + bookkeeping_path alt_f_path = alt_bookkeeping_root / "format"; +#endif // one or other side of this conditional will always be dead code, but // both sides should be preserved, to document all historical formats. // N.B. this will _not_ do the right thing for format 0. Which is fine. @@ -99,10 +119,19 @@ workspace::write_ws_format() { if (file_exists(f_path)) delete_file(f_path); +#if defined(RSE) /* alt-book-keeping-root */ + if (file_exists(alt_f_path)) + delete_file(alt_f_path); +#endif } else { data f_dat(lexical_cast(current_workspace_format) + "\n"); +#if defined(RSE) /* alt-book-keeping-root */ + if (directory_exists(alt_bookkeeping_root)) + write_data(alt_f_path, f_dat); + else +#endif write_data(f_path, f_dat); } } @@ -185,7 +214,11 @@ migrate_1_to_2() // information, and _MTN/work does not exist; also, there may be more than // one parent revision, but we do not have to worry about that here. +#if defined(RSE) /* alt-book-keeping-root */ + bookkeeping_path rev_path = (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / "revision"; +#else bookkeeping_path rev_path = bookkeeping_root / "revision"; +#endif data base_rev_data; MM(base_rev_data); try { @@ -201,7 +234,11 @@ migrate_1_to_2() cset workcs; MM(workcs); +#if defined(RSE) /* alt-book-keeping-root */ + bookkeeping_path workcs_path = (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / "work"; +#else bookkeeping_path workcs_path = bookkeeping_root / "work"; +#endif bool delete_workcs = false; if (file_exists(workcs_path)) {