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. o lua-rel-path: Support for additional Lua function workspace_relpath() for receiving the "initial relative path" within the workspace (which is important to know because in a Lua hook the current working directory was changed to the workspace root). Ralf S. Engelschall rse@engelschall.com www.engelschall.com =================================================================== Index: cmd_diff_log.cc --- cmd_diff_log.cc 37b1cfffa001ccc5b813fe6f0e84dc6a5dc3996e +++ cmd_diff_log.cc 4d6a26b3146f903df899a4777f8f556c7e5704e4 @@ -99,6 +99,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(); @@ -116,6 +137,7 @@ print_indented_set(ostream & os, cols += str.size() + 1; } os << '\n'; +#endif } void @@ -134,7 +156,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'; } @@ -229,7 +255,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, @@ -240,8 +271,13 @@ dump_diffs(lua_hooks & lua, bool show_encloser, bool limit_paths) { +#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(); @@ -250,6 +286,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; @@ -296,6 +335,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'; if (old_is_archived) @@ -342,7 +384,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, @@ -352,8 +399,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, old_is_archived, show_encloser, false); +#else dump_diffs(lua, db, cs, dummy, output, diff_format, new_is_archived, old_is_archived, show_encloser, false); +#endif } // common functionality for diff and automate content_diff to determine @@ -569,9 +621,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, old_is_archived, + !app.opts.no_show_encloser); +#else dump_diffs(app.lua, db, included, cout, app.opts.diff_format, new_is_archived, old_is_archived, !app.opts.no_show_encloser); +#endif } } @@ -606,8 +664,13 @@ CMD_AUTOMATE(content_diff, N_("[FILE [.. dump_header(dummy_header, included, output, false); } +#if defined(RSE) /* cosmetics-diff-and-log */ + dump_diffs(app, app.lua, db, included, output, + app.opts.diff_format, new_is_archived, old_is_archived, !app.opts.no_show_encloser); +#else dump_diffs(app.lua, db, included, output, app.opts.diff_format, new_is_archived, old_is_archived, !app.opts.no_show_encloser); +#endif } @@ -1048,7 +1111,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; @@ -1063,12 +1130,23 @@ 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'; +#else out << _("Ancestor: ") << *anc << '\n'; +#endif +#if defined(RSE) /* cosmetics-diff-and-log */ + log_certs(certs, out, author_name, _("Author: "), false); + log_date_certs(certs, out, date_fmt, _("Date: "), false); + log_certs(certs, out, branch_name, _("Branch: "), false); + log_certs(certs, out, tag_name, _("Tag: "), false); +#else log_certs(certs, out, author_name, _("Author: "), false); log_date_certs(certs, out, date_fmt, _("Date: "), false); log_certs(certs, out, branch_name, _("Branch: "), false); log_certs(certs, out, tag_name, _("Tag: "), false); +#endif if (!app.opts.no_files && !csum.cs.empty()) { @@ -1078,16 +1156,26 @@ CMD(log, "log", "", CMD_REF(informative) } log_certs(certs, out, changelog_name, _("ChangeLog: "), true); +#if defined(RSE) /* cosmetics-diff-and-log */ + log_certs(certs, out, comment_name, _("Comments: "), true); +#else log_certs(certs, out, 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, 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, true, !app.opts.no_show_encloser, !mask.empty()); +#endif } if (next > 0) =================================================================== Index: cmd_netsync.cc --- cmd_netsync.cc e70435eb00dcd606984f40ca8b4a53996985fc72 +++ cmd_netsync.cc 182df7fa258a5858106a8eb038fe4f84083e813b @@ -519,7 +519,11 @@ CMD(pull, "pull", "", CMD_REF(network), args, info, false); if (!keys.have_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, app.opts, app.lua, project, keys, client_voice, sink_role, info); @@ -698,9 +702,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.branch must be set here // otherwise it will not be written into _MTN/options, in case @@ -725,7 +736,11 @@ CMD(clone, "clone", "", CMD_REF(network) info, true, true, false); if (!keys.have_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 8f12395a73d4c2fbd19e4eac883b719e581d0ae7 +++ cmd_ws_commit.cc 490bc6a101d7de1b85203d5c3a135fafbbff59a4 @@ -94,7 +94,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) @@ -1493,12 +1500,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_output.cc --- diff_output.cc 746af1eeaa7fee943bf1264dc88ddd6e218c7866 +++ diff_output.cc a2e01d5f3517b7393711c3bfca7e7eb5a8b9772b @@ -566,6 +566,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' @@ -577,6 +580,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 6683cf8d459a89597caf1eb3ce01800fe4404643 +++ file_io.cc 4faf0094870242fac6a9d1543e2239448453f407 @@ -342,16 +342,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 d4f06492ebfd19893156cf0021b7afef29009332 +++ lua_hooks.cc 1041f875567d43f4afd0c5e68c10f96ebecfd219 @@ -224,7 +224,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: luaext_platform.cc --- luaext_platform.cc b22b5f7fa127055e909280379694c1635f4923ff +++ luaext_platform.cc 24765cd8252f31a829d450ef92e002e615c98f9d @@ -14,6 +14,9 @@ #include #include "platform.hh" +#if defined(RSE) /* lua-rel-path */ +#include "paths.hh" +#endif using std::malloc; using std::free; @@ -185,6 +188,15 @@ LUAEXT(get_pid, ) return 1; } +#if defined(RSE) /* lua-rel-path */ +LUAEXT(workspace_relpath, ) +{ + std::string str = workspace_initial_relative_path(); + lua_pushlstring(LS, str.c_str(), str.size()); + return 1; +} +#endif + // Local Variables: // mode: C++ // fill-column: 76 =================================================================== Index: merge_roster.cc --- merge_roster.cc 9c8e34b5521bde6f9a38f2996ce0edf51273e98d +++ merge_roster.cc e653e463f7351026b9c90f32962e44f6f316a77c @@ -724,6 +724,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 } } =================================================================== Index: migrate_work.cc --- migrate_work.cc 2a8658dad954b975e65d3ceaf5df608007bfe841 +++ migrate_work.cc dc14b6fd1d3f80df37ae42f9df1be1253f085724 @@ -56,9 +56,18 @@ get_workspace_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; @@ -70,7 +79,11 @@ get_workspace_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_workspace_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_format() workspace::write_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,11 +119,20 @@ workspace::write_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", origin::workspace); +#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); } } @@ -186,7 +215,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 { @@ -203,7 +236,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)) { =================================================================== Index: options_list.hh --- options_list.hh 521e90001833b8023276b7cedd0ed4b3253c1fd4 +++ options_list.hh e58b1dbae2c4089d3fbfed73f5a9981b9fd02b1e @@ -331,6 +331,24 @@ OPTION(diff_options, with_header, false, without_header = false; } #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 defa5bd893d6db66107e50122713ee2769ce8d15 +++ paths.cc ca77119868c66526ccb6c6e024f9c242075774ff @@ -231,7 +231,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.empty() || (path[0] != '_' && path[0] != '.')) +#else if (path.empty() || (path[0] != '_')) +#endif return false; if (path.size() == 1 || (path[1] != 'M' && path[1] != 'm')) return false; @@ -1024,6 +1028,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; @@ -1054,6 +1061,13 @@ mark_std_paths_used(void) initial_rel_path.get(); } +#if defined(RSE) /* lua-rel-path */ +string workspace_initial_relative_path(void) +{ + return initial_rel_path.get(); +} +#endif + /////////////////////////////////////////////////////////////////////////// // utility used by migrate_ancestry /////////////////////////////////////////////////////////////////////////// =================================================================== Index: paths.hh --- paths.hh e92b013f510afba1da74ab8f47a8f2fa799b3b5c +++ paths.hh 38da34092ece2d3f80317b97197630711c05e393 @@ -351,6 +351,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 { @@ -473,8 +482,12 @@ find_new_path_for(std::map const & renames, file_path const & old_path); +#if defined(RSE) /* lua-rel-path */ +std::string workspace_initial_relative_path(void); #endif +#endif + // Local Variables: // mode: C++ // fill-column: 76 =================================================================== Index: std_hooks.lua --- std_hooks.lua 8d713fef5bf9921b78ac24e07f6d837524e35e9a +++ std_hooks.lua 1c5fe914add9d8608b50cab633592ad5165688f3 @@ -304,6 +304,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) @@ -1442,3 +1451,267 @@ end return false 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|integrate}" +register_command( + "base", "fork|upgrade|diff|integrate [BRANCHNAME|FILENAME]", + "Forks, upgrades, compares or integrates current branch from/against/into base branch", + "Forks a new branch from the current branch, " .. + "upgrade current branch from base branch, " .. + "or compares current branch against base branch, " .. + "or integrates current branch into 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, arg) + -- sanity check command line + if op == nil then + io.stderr:write("mtn: base: ERROR: no operation specified\n") + return + end + if op ~= "fork" and op ~= "upgrade" and op ~= "diff" and op ~= "integrate" then + io.stderr:write("mtn: base: ERROR: either \"fork\", \"upgrade\", or \"diff\" or \"integrate\" operation has to be specified\n") + return + end + if op == "fork" and arg == nil then + io.stderr:write("mtn: base: ERROR: BRANCHNAME argument missing for \"fork\" operation\n") + return + end + if op == "upgrade" and arg ~= nil then + io.stderr:write("mtn: base: ERROR: argument not valid for \"upgrade\" operation\n") + return + end + if op == "integrate" and arg ~= nil then + io.stderr:write("mtn: base: ERROR: argument not valid for \"integrate\" operation\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 + if op == "upgrade" or op == "diff" or op == "integrate" then + 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 + end + + -- dispatch according to operation + if op == "fork" then + -- fork new branch from current branch + io.stderr:write("mtn: base: fork current branch \"" .. branch_this .. "\" into new branch \"" .. arg .. "\"\n") + fh = io.open(".mtn-base", "w") + fh:write(branch_this) + io.close(fh) + local rc, txt = mtn_automate("get_attributes", ".mtn-base") + if rc ~= 0 then + -- .mtn-base still not existing in current branch + local rc = execute("mtn", "add", ".mtn-base") + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn add\"\n") + return + end + end + rc = execute("mtn", "commit", "-m", "[auto-commit] add hint-file \".mtn-base\" to branch \"" .. arg .. "\"", "-b", arg, ".mtn-base") + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn commit\"\n") + return + end + elseif op == "upgrade" then + -- upgrade current branch by merging in revisions of base branch + io.stderr:write("mtn: base: upgrade current branch \"" .. branch_this .. "\" from base branch \"" .. branch_base .. "\"\n") + 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 + io.stderr:write("mtn: base: diff current branch \"" .. branch_this .. "\" against base branch \"" .. branch_base .. "\"\n") + local rc + if arg ~= nil then + if not string.sub(arg, 1, 1) ~= "/" then + arg = workspace_relpath() .. "/" .. arg + end + rc = execute("mtn", "diff", "-r", "h:" .. branch_base, "-r", "h:" .. branch_this, arg) + else + rc = execute("mtn", "diff", "-r", "h:" .. branch_base, "-r", "h:" .. branch_this) + end + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn diff\"\n") + return + end + elseif op == "integrate" then + -- integrate back current branch by merging its revision into the base branch + io.stderr:write("mtn: base: integrate current branch \"" .. branch_this .. "\" into base branch \"" .. branch_base .. "\"\n") + rc = execute("mtn", "rm", ".mtn-base") + rc = execute("mtn", "commit", "-m", "[auto-commit] remove hint-file \".mtn-base\" from branch \"" .. branch_this .. "\"", ".mtn-base") + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn commit\"\n") + return + end + local rc = execute("mtn", "propagate", branch_this, branch_base) + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn propagate\"\n") + return + end + local rc = execute("mtn", "update", "-b", branch_base, "-r", "h:" .. branch_base) + if rc ~= 0 then + io.stderr:write("mtn: base: ERROR: failed to execute \"mtn update\"\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: unit-tests/merge_roster.cc --- unit-tests/merge_roster.cc fd6d157c77b4f168bdc1ed29940beea69707e1a3 +++ unit-tests/merge_roster.cc 8ff5b6ffeab2c6b0f3a00e89e2f6c05c6de7f2ce @@ -945,7 +945,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: unit-tests/paths.cc --- unit-tests/paths.cc f443111b016125fb2b00af638586853a68369ad9 +++ unit-tests/paths.cc c14f0b1d89ba65c86af3b3f1591aa7eb694ce126 @@ -531,7 +531,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(), =================================================================== Index: work.cc --- work.cc 4a27d0c0133f6cc04b06944afe080400095676e2 +++ work.cc b7d8a57ea2935bc09f2e2358fa96685dee44737b @@ -60,42 +60,66 @@ 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); } static void get_update_path(bookkeeping_path & update_path) { +#if defined(RSE) /* alt-book-keeping-root */ + update_path = (directory_exists(alt_bookkeeping_root) ? alt_bookkeeping_root : bookkeeping_root) / update_file_name; +#else update_path = bookkeeping_root / update_file_name; +#endif L(FL("update path is %s") % update_path); } @@ -113,7 +137,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; @@ -146,13 +174,24 @@ workspace::create_workspace(options cons go_to_workspace(new_dir); mark_std_paths_used(); +#if defined(RSE) /* alt-book-keeping-root */ + E(!(directory_exists(bookkeeping_root) || directory_exists(alt_bookkeeping_root)), origin::user, + F("monotone bookkeeping directory '%s' or '%s' already exists in '%s'") + % bookkeeping_root % alt_bookkeeping_root % new_dir); +#else E(!directory_exists(bookkeeping_root), origin::user, 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; @@ -545,9 +584,16 @@ workspace::get_database_option(system_pa external_key_name workspace_key; system_path workspace_keydir; +#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, workspace_database, workspace_branch, workspace_key, workspace_keydir); @@ -745,7 +791,11 @@ workspace::get_local_dump_path(bookkeepi { E(workspace::found, origin::user, 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); } @@ -1170,7 +1220,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 @@ -1974,9 +2028,15 @@ workspace::perform_pivot_root(database & E(is_dir_t(old_roster.get_node(new_root)), origin::user, F("proposed new root directory '%s' is not a directory") % new_root); { +#if defined(RSE) /* alt-book-keeping-root */ + E(!(new_roster.has_node(new_root / bookkeeping_root_component) || new_roster.has_node(new_root / alt_bookkeeping_root_component)), origin::user, + F("proposed new root directory '%s' contains illegal path %s or %s") + % new_root % bookkeeping_root % alt_bookkeeping_root); +#else E(!old_roster.has_node(new_root / bookkeeping_root_component), origin::user, F("proposed new root directory '%s' contains illegal path %s") % new_root % bookkeeping_root); +#endif } {