
  This is the RSE patchset for the Monotone VCS.

  The patchset is entirely enclosed in...
  
    #if defined(RSE) /* <tag> */
    [...]
    #endif

  ...and this way can be clearly distinguished. The <tag> 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	aac2aabe76bf2ba7b53a07325861479f56f8ff9d
+++ cmd_diff_log.cc	3cc3c603379b203917ace5c1f6f5fa7b76b4e458
@@ -101,6 +101,27 @@ print_indented_set(ostream & os,
                    set<file_path> const & s,
                    size_t max_cols)
 {
+#if defined(RSE) /* cosmetics-diff-and-log */
+  size_t cols = 4;
+  os << "    ";
+  for (set<file_path>::const_iterator i = s.begin();
+       i != s.end(); i++)
+    {
+      const string str = lexical_cast<string>(*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<file_path>::const_iterator i = s.begin();
@@ -118,6 +139,7 @@ print_indented_set(ostream & os,
       cols += str.size() + 1;
     }
   os << '\n';
+#endif
 }
 
 void
@@ -136,7 +158,11 @@ changes_summary::print(ostream & os, siz
       for (map<file_path, file_path>::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';
     }
 
@@ -231,7 +257,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<file_path> const & paths,
@@ -242,8 +273,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<file_path, file_id>::const_iterator
          i = cs.files_added.begin();
@@ -252,6 +288,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<string> lines;
@@ -298,6 +337,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)
@@ -344,7 +386,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,
@@ -354,8 +401,13 @@ dump_diffs(lua_hooks & lua,
            bool show_encloser)
 {
   set<file_path> 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
@@ -571,9 +623,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
     }
 }
 
@@ -608,8 +666,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
 }
 
 
@@ -1087,7 +1150,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;
 
@@ -1104,10 +1171,17 @@ CMD(log, "log", "", CMD_REF(informative)
                    anc != ancestors.end(); ++anc)
                 out << "Ancestor: " << *anc << '\n';
 
+#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())
                 {
@@ -1117,16 +1191,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	a45569ce4b7735f449ed9b05e3e12ac7c0457c97
+++ cmd_netsync.cc	39d5b5db57238c9b6ee526b16870185020e4ee40
@@ -265,7 +265,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.opts, app.lua, project, keys,
                        client_voice, sink_role, info);
@@ -400,9 +404,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
@@ -427,7 +438,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	8e8a55dc2e283f4ee01f2b862f38f574d330a9b9
+++ cmd_ws_commit.cc	3fd254a1e313ebcf8c84963381bdbeb356ed4426
@@ -73,7 +73,14 @@ revision_summary(revision_t const & rev,
 
       for (set<file_path>::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<file_path, file_id>::const_iterator i = cs.files_added.begin();
             i != cs.files_added.end(); ++i)
@@ -1431,12 +1438,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	1c79107b3af55f17b13b5dae75d1e5dc3448e59f
+++ file_io.cc	dab3264acc2d2ebf9a5a29db8b75ad57f89a0d40
@@ -420,16 +420,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	071959bce2f7b86e171fb65e35f536e32959b61c
+++ lua_hooks.cc	db3cd988144a9547dfbd203d7cf7d123cca10e74
@@ -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 <cstdlib>
 
 #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	e54910bfbd63ee800962d313e433fc79ad3534c1
+++ merge_roster.cc	81da5df67cdc3f82ab59cfad104bba6174b7aa28
@@ -723,6 +723,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<unsigned int>(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<string>(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	556f0e2b7f83c5baece9886bcf2ef842ef2fc29e
+++ options_list.hh	3e8b26764f9e2191a5b3bbd4e12c7dcdea80d19f
@@ -313,6 +313,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	29c4401e73c9b708fb3b95c41677c8b46e0d43af
+++ paths.cc	bacf142f6c005eecab5944a0019f5c7b0bdd4647
@@ -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;
@@ -1000,6 +1004,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;
@@ -1030,6 +1037,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	7405e173fac28aa9974fd515a06b7d66e1b3fa67
+++ paths.hh	abcea42550cdf0b63a7dfe099e14527daf1b59db
@@ -350,6 +350,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
 {
@@ -449,8 +458,12 @@ find_new_path_for(std::map<file_path, fi
 find_new_path_for(std::map<file_path, file_path> 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	4fd91de30f180e1c8bd1b790fa81f642ea839d55
+++ std_hooks.lua	f948c697c2d4726f703ced401adae74897bfc4a0
@@ -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)
 
@@ -1399,3 +1408,267 @@ 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|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	1853c103dd728750136e85ab5e97f72104fc0d7f
+++ unit-tests/merge_roster.cc	c4e4b238de545a1b99328548aed099306b085deb
@@ -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	7badc3da940f8ef7f9ed821adca729c8b5cad408
+++ work.cc	fbb89f6243f4563875fe7ffc948d8c8d7bdc90be
@@ -59,42 +59,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);
 }
 
@@ -105,7 +129,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;
@@ -138,13 +166,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;
@@ -521,9 +560,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);
@@ -605,7 +651,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);
 }
 
@@ -1018,7 +1068,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
@@ -1819,9 +1873,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
   }
 
   {
