The unified diff between revisions [dd2f27b8..] and [016b3b68..] is displayed below. It can also be downloaded as a raw diff.

#
#
# rename "tests/t_log_nofiles_merges.at"
#     to "tests/t_log_nofiles_nomerges.at"
#
# rename "tests/t_sql_unpack.at"
#     to "tests/t_sql_gunzip.at"
#
# add_file "tests/t_log_selectors.at"
#  content [3d000a79a2acdff3df5c7a8a804d2e38aa035dc5]
#
# add_file "tests/t_pivot_root.at"
#  content [2bf3634c3c1a16c375a85bc5f403992422cdfc8b]
#
# add_file "tests/t_pivot_root_revert.at"
#  content [bf2c402b7cb49ebdd178928ea01372deb68858a5]
#
# add_file "tests/t_pivot_root_update.at"
#  content [e4f51059ec68ec4f187cd3593eeb49459aea3b5a]
#
# add_file "tests/t_rosterify_root_suture.at"
#  content [ff625956c64676b2b33f9ccc9f9a45b7b7587318]
#
# patch "ChangeLog"
#  from [e130fcd32c009c5f7ad8b31d5736aed3e9f67c09]
#    to [cef9fe8e359d630eb8f61133684772293748eade]
#
# patch "app_state.cc"
#  from [d6bd147ff69418001cff7cfd08e7ec18deca23ab]
#    to [4990c26751504dded9cef9b1c6ffa66bef0d2b32]
#
# patch "app_state.hh"
#  from [7fb16348486ca507f22a99d6aca9ba2dc4a658c0]
#    to [03afbc7ba543f0b26f8ddf70d97c05e872cdb36b]
#
# patch "commands.cc"
#  from [8d15447176db8827209c2feeaaf0b0eaa7e2e1fd]
#    to [0fc12a37debd7db7812e2670afcb99d787f869e0]
#
# patch "contrib/color-logs.conf"
#  from [ffe3e7270a2952c508db2f2d9c3df82e05c859f4]
#    to [87e8ceac1e0a8d173e2a0da01e659d048824e9ed]
#
# patch "contrib/color-logs.sh"
#  from [90a0de6aa87b7f4b39f7947fd1d359a3cc936385]
#    to [4e04daeee4ee8ce8bf6ce45f074ba16faf50ccf9]
#
# patch "contrib/monotone-notify.pl"
#  from [aa93d7f9f601b51d607bd4d779e472067c41d5a2]
#    to [d7f551f0f332f56785aaa59e99ade7400c8da84a]
#
# patch "contrib/monotone.el"
#  from [3dc1c23d499d1aa015ed7f145416a241a55f054f]
#    to [9e1a1ec218b98cd38c6126c48f00a1d585e65a13]
#
# patch "cset.cc"
#  from [a8497f9a82330f842cda2f242e97ef48e3a6b9fb]
#    to [5fdac8f01ad19e4a427e40aa4245026b534422f7]
#
# patch "database.cc"
#  from [70d4d8f0c43de5e18bb023fc2c87805c0cdfa659]
#    to [61bac503d11d0d68da007b53ec4a7f31e0453cdc]
#
# patch "file_io.cc"
#  from [3214c2cad3ec630d8466926bd4bae693de9184a0]
#    to [3ca8050b61b62ef9010b0ebfda755a0af9269104]
#
# patch "file_io.hh"
#  from [2082dc3c8480400f32fcbe1c4d881d7512de2204]
#    to [362691f10f63d93e7cf83f299fe5aa0b4dcbee17]
#
# patch "monotone.cc"
#  from [6626e22d6ef4d9c8d3380a2f498599dd57bc6d3d]
#    to [29890d9e90e828fa96d845cb881bb024a8267d99]
#
# patch "monotone.texi"
#  from [1fcc35f2ac3ca9ba692509cd29f6f5ff01b6616a]
#    to [d62e740d5bf36c317e4ec3fea155132bd7ba702d]
#
# patch "netcmd.cc"
#  from [5a63ccd88ddb02cf5853e13ce3fff6ea20851a53]
#    to [da3773c93069c0834ce0b3caf34020305dd7d8b2]
#
# patch "netcmd.hh"
#  from [22664387cea0916b87288113ec5b559c2c49eda5]
#    to [e290b45f5acb01bfe181832d4750275e05b2f98f]
#
# patch "options.hh"
#  from [16194449c59ae0307ab9bbf15ae879f0f8063fdd]
#    to [216cb82f6f82b63b0f1535b39bb985a237b43359]
#
# patch "paths.cc"
#  from [c13e9b0c44a37a70e6c7e6d5c8b8869e363ccab9]
#    to [05c27908836d28a468070bb8e7a808417b8401f1]
#
# patch "paths.hh"
#  from [054a74b153b42a34140f2776f468bf79c844fa64]
#    to [e81d1f0e7efdbff539b8b92f04ad357770130fd4]
#
# patch "roster.cc"
#  from [5313de9037e0af779b593f9f432155891a70374a]
#    to [ccbdfd50a2a99c49598ae638a013a095f232c876]
#
# patch "roster.hh"
#  from [0647777b7cab5a2c17419998351f049eadc706d5]
#    to [9ec5d3bbd5751f987a7591a3d577002a627a36e3]
#
# patch "roster_merge.cc"
#  from [aa38469bceafec9e94d2ba9c291cf08d6fc90e06]
#    to [d1d307d2cc0fac8f4c50963b1563edcc3df26d86]
#
# patch "roster_merge.hh"
#  from [9227745f9aeb67c68ca363dc1df62537c8bcb873]
#    to [3a86138ac40adb1f2736fc9f348988b91aea2ff4]
#
# patch "schema.sql"
#  from [5e315b54e2c1df1f315c3aff2ad9a551332debb7]
#    to [c68ba3a5f158efdf6cae10d6b307a9cf0d35be44]
#
# patch "schema_migration.cc"
#  from [9f3a9b8a4a509edf8af01c796cbc6588e902e888]
#    to [7d215b15eb77f65457092c0cc1553f4ab2689a4e]
#
# patch "std_hooks.lua"
#  from [87a925a89f17ae312390049e28296bbb9f7ee170]
#    to [4ca1188ac09063710baa429986649f2c3ad13083]
#
# patch "tests/t_log_nofiles_nomerges.at"
#  from [958b5e4229b1afd2c7ec131d0a239b28d792815f]
#    to [dedc7b333ac5e779f29464a1f62285ec787b6dd4]
#
# patch "tests/t_migrate_schema.at"
#  from [9d9a73cc20f8e367f4c828484e78c19224614b93]
#    to [e5b24dd9ccc44e36b20d1b1dc89509f0e99896fd]
#
# patch "tests/t_sql_gunzip.at"
#  from [1bb994c7c10fcfafc2a0fc9b5dd7c45b5b315174]
#    to [1e43988cec90fe9e16a51379b7b832cfc609d122]
#
# patch "testsuite.at"
#  from [c8be08d87b48f90c833c326e708aaa06423b1b75]
#    to [d67ef3223629641b02551a0a31cbf4644cd60d82]
#
# patch "work.cc"
#  from [39e5c9ecd7f5209dbcbb7e631d6fc044296a4a67]
#    to [be9eb83e519a1395626b2b29868af63e46a958c2]
#
# patch "work.hh"
#  from [2e87d93b08c16ef877483f69594636c480d0f97f]
#    to [dc6f1035c0b3a27f9b94bff02d5f650b9da8f26b]
#
============================================================
--- tests/t_log_selectors.at	3d000a79a2acdff3df5c7a8a804d2e38aa035dc5
+++ tests/t_log_selectors.at	3d000a79a2acdff3df5c7a8a804d2e38aa035dc5
@@ -0,0 +1,26 @@
+AT_SETUP([log and selectors returning multiple rids])
+MONOTONE_SETUP
+
+# testcase for bug #15877
+
+ADD_FILE(testfile, [blah blah
+])
+AT_CHECK(MONOTONE commit -b testbranch --date "2005-08-16T03:16:00" -m foo, [], [ignore], [ignore])
+R0=`BASE_REVISION`
+
+SET_FILE(testfile, [stuff stuff
+])
+AT_CHECK(MONOTONE commit -b testbranch --date "2005-08-16T03:16:00" -m foo, [], [ignore], [ignore])
+R1=`BASE_REVISION`
+
+SET_FILE(testfile, [other other
+])
+AT_CHECK(MONOTONE commit -b otherbranch --date "2005-08-16T03:16:05" -m foo, [], [ignore], [ignore])
+R2=`BASE_REVISION`
+
+AT_CHECK(RAW_MONOTONE --db=$_ROOT_DIR/test.db --root=$_ROOT_DIR log --brief --revision d:2005-08-16, [], [stdout], [ignore])
+AT_CHECK(grep $R0 stdout, [0], [ignore])
+AT_CHECK(grep $R1 stdout, [0], [ignore])
+AT_CHECK(grep $R2 stdout, [0], [ignore])
+
+AT_CLEANUP
============================================================
--- tests/t_pivot_root.at	2bf3634c3c1a16c375a85bc5f403992422cdfc8b
+++ tests/t_pivot_root.at	2bf3634c3c1a16c375a85bc5f403992422cdfc8b
@@ -0,0 +1,52 @@
+AT_SETUP([pivot_root])
+MONOTONE_SETUP
+
+# possible problems:
+#   -- the new root doesn't exist
+#   -- the new root is not a dir
+#   -- the new root has an MT in it
+#   -- the directory the old root is supposed to end up in doesn't exist
+#   -- the directory the old root is supposed to end up in is not a directory
+#   -- the directory the old root is supposed to end up in already
+#      contains something with the given name
+# then make sure --execute puts things in the right place...
+
+AT_CHECK(mkdir workspace)
+AT_CHECK(cd workspace/ && MONOTONE setup . -b testbranch, [], [ignore], [ignore])
+
+AT_CHECK(mkdir workspace/dir1)
+AT_CHECK(mkdir workspace/dir1/dir2)
+AT_DATA(workspace/dir1/file1, [blah blah
+])
+AT_CHECK(mkdir workspace/dir3)
+AT_CHECK(mkdir workspace/dir3/MT)
+AT_CHECK(cd workspace/ && MONOTONE add ., [], [ignore], [ignore])
+
+AT_CHECK(cd workspace/ && MONOTONE commit -m foo, [], [ignore], [ignore])
+
+AT_CHECK(cd workspace/ && MONOTONE pivot_root nosuchdir foo, [1], [ignore], [ignore])
+AT_CHECK(cd workspace/ && MONOTONE pivot_root dir1/file1 foo, [1], [ignore], [ignore])
+AT_CHECK(cd workspace/ && MONOTONE pivot_root dir3 old_root, [1], [ignore], [ignore])
+AT_CHECK(cd workspace/ && MONOTONE pivot_root dir1 nosuchdir/old_root, [1], [ignore], [ignore])
+AT_CHECK(cd workspace/ && MONOTONE pivot_root dir1 file1/old_root, [1], [ignore], [ignore])
+AT_CHECK(cd workspace/ && MONOTONE pivot_root dir1 dir2, [1], [ignore], [ignore])
+
+AT_CHECK(cd workspace/ && MONOTONE ls changed, [], [], [])
+AT_CHECK(cd workspace/ && MONOTONE ls missing, [], [], [])
+AT_CHECK(cd workspace/ && MONOTONE ls unknown, [], [], [])
+
+AT_CHECK(cd workspace/ && MONOTONE pivot_root --execute dir1 old_root, [], [ignore], [ignore])
+
+AT_CHECK(test -d workspace/MT)
+AT_CHECK(test -d workspace/dir2)
+AT_CHECK(test -f workspace/file1)
+AT_CHECK(test -d workspace/old_root)
+AT_CHECK(test -d workspace/old_root/dir3)
+AT_CHECK(test -d workspace/old_root/dir3/MT)
+
+AT_CHECK(cd workspace/ && MONOTONE ls missing, [], [], [])
+AT_CHECK(cd workspace/ && MONOTONE ls unknown, [], [], [])
+
+AT_CHECK(cd workspace/ && MONOTONE commit -m foo, [], [ignore], [ignore])
+
+AT_CLEANUP
============================================================
--- tests/t_pivot_root_revert.at	bf2c402b7cb49ebdd178928ea01372deb68858a5
+++ tests/t_pivot_root_revert.at	bf2c402b7cb49ebdd178928ea01372deb68858a5
@@ -0,0 +1,47 @@
+AT_SETUP([reverting a pivot_root])
+MONOTONE_SETUP
+
+# This test is a bug report
+# I think the problem is just generally that revert does not do a good
+# job cleaning up after renames?
+AT_XFAIL_IF(true)
+
+AT_CHECK(mkdir workspace)
+AT_CHECK(cd workspace/ && MONOTONE setup . -b testbranch, [], [ignore], [ignore])
+
+AT_CHECK(mkdir workspace/dir1)
+AT_CHECK(mkdir workspace/dir1/dir2)
+AT_DATA(workspace/dir1/file1, [blah blah
+])
+AT_CHECK(mkdir workspace/dir3)
+AT_CHECK(mkdir workspace/dir3/MT)
+AT_CHECK(cd workspace/ && MONOTONE add ., [], [ignore], [ignore])
+
+AT_CHECK(cd workspace/ && MONOTONE commit -m foo, [], [ignore], [ignore])
+
+AT_CHECK(cd workspace/ && MONOTONE pivot_root --execute dir1 old_root, [], [ignore], [ignore])
+
+AT_CHECK(test -d workspace/MT)
+AT_CHECK(test -d workspace/dir2)
+AT_CHECK(test -f workspace/file1)
+AT_CHECK(test -d workspace/old_root)
+AT_CHECK(test -d workspace/old_root/dir3)
+AT_CHECK(test -d workspace/old_root/dir3/MT)
+
+AT_CHECK(cd workspace/ && MONOTONE ls missing, [], [], [])
+AT_CHECK(cd workspace/ && MONOTONE ls unknown, [], [], [])
+
+AT_CHECK(cd workspace/ && MONOTONE revert ., [], [ignore], [ignore])
+
+AT_CHECK(test -d workspace/MT)
+AT_CHECK(test -d workspace/dir1)
+AT_CHECK(test -d workspace/dir1/dir2)
+AT_CHECK(test -f workspace/dir1/file1)
+AT_CHECK(test -d workspace/dir3)
+AT_CHECK(test -d workspace/dir3/MT)
+
+AT_CHECK(cd workspace/ && MONOTONE ls changed, [], [], [])
+AT_CHECK(cd workspace/ && MONOTONE ls missing, [], [], [])
+AT_CHECK(cd workspace/ && MONOTONE ls unknown, [], [], [])
+
+AT_CLEANUP
============================================================
--- tests/t_pivot_root_update.at	e4f51059ec68ec4f187cd3593eeb49459aea3b5a
+++ tests/t_pivot_root_update.at	e4f51059ec68ec4f187cd3593eeb49459aea3b5a
@@ -0,0 +1,49 @@
+AT_SETUP([updating through a pivot_root])
+MONOTONE_SETUP
+
+AT_CHECK(mkdir workspace)
+AT_CHECK(cd workspace/ && MONOTONE setup -b testbranch, [], [ignore], [ignore])
+
+AT_CHECK(mkdir workspace/dir1)
+AT_DATA(workspace/old_root_file, [I'm in the root to start off with!
+])
+AT_DATA(workspace/dir1/new_root_file, [I'm in the subdir to start off with.
+])
+AT_CHECK(cd workspace/ && MONOTONE add ., [], [ignore], [ignore])
+AT_CHECK(cd workspace/ && MONOTONE commit -m foo, [], [ignore], [ignore])
+BASE_REV=`cat workspace/MT/revision`
+
+AT_CHECK(cd workspace/ && MONOTONE pivot_root --execute dir1 old_root, [], [ignore], [ignore])
+AT_CHECK(cd workspace/ && MONOTONE commit -m foo, [], [ignore], [ignore])
+
+AT_CHECK(MONOTONE co -r $BASE_REV testspace, [], [ignore], [ignore])
+AT_DATA(new_old_root_file, [old root file modified
+])
+AT_DATA(new_new_root_file, [new root file modified
+])
+AT_DATA(new_unversioned_root_file, [newly placed in root dir, unversioned
+])
+AT_DATA(new_unversioned_subdir_file, [newly placed in sub dir, unversioned
+])
+AT_DATA(new_versioned_root_file, [newly placed in root dir, versioned
+])
+AT_DATA(new_versioned_subdir_file, [newly placed in sub dir, versioned
+])
+AT_CHECK(cp new_old_root_file testspace/old_root_file)
+AT_CHECK(cp new_new_root_file testspace/dir1/new_root_file)
+AT_CHECK(cp new_unversioned_root_file testspace)
+AT_CHECK(cp new_unversioned_subdir_file testspace/dir1)
+AT_CHECK(cp new_versioned_root_file testspace)
+AT_CHECK(cp new_versioned_subdir_file testspace/dir1)
+
+AT_CHECK(cd testspace/ && MONOTONE add new_versioned_root_file dir1/new_versioned_subdir_file, [], [ignore], [ignore])
+AT_CHECK(cd testspace/ && MONOTONE update, [], [ignore], [ignore])
+
+AT_CHECK(cmp new_old_root_file testspace/old_root/old_root_file)
+AT_CHECK(cmp new_new_root_file testspace/new_root_file)
+AT_CHECK(cmp new_unversioned_root_file testspace/old_root/new_unversioned_root_file)
+AT_CHECK(cmp new_unversioned_subdir_file testspace/new_unversioned_subdir_file)
+AT_CHECK(cmp new_versioned_root_file testspace/old_root/new_versioned_root_file)
+AT_CHECK(cmp new_versioned_subdir_file testspace/new_versioned_subdir_file)
+
+AT_CLEANUP
============================================================
--- tests/t_rosterify_root_suture.at	ff625956c64676b2b33f9ccc9f9a45b7b7587318
+++ tests/t_rosterify_root_suture.at	ff625956c64676b2b33f9ccc9f9a45b7b7587318
@@ -0,0 +1,124 @@
+AT_SETUP([db rosterify on a db with a root suture])
+MONOTONE_SETUP
+NEED_UNGZB64
+
+AT_DATA([test.db.dump.gz.b64], [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
+])
+
+AT_CHECK(rm -f test.db)
+
+UNGZB64(test.db.dump.gz.b64, test.db.dump)
+AT_CHECK(MONOTONE db load < test.db.dump, [], [ignore], [ignore])
+AT_CHECK(MONOTONE db migrate, [], [ignore], [ignore])
+AT_CHECK(MONOTONE db rosterify, [], [ignore], [ignore])
+
+AT_CHECK(for REV in `MONOTONE automate select i:`; do MONOTONE automate get_revision $REV; done, [], [stdout], [ignore])
+AT_CHECK(QGREP('delete ""' stdout))
+AT_CHECK(QGREP('add_dir ""' stdout))
+
+AT_CLEANUP
============================================================
--- ChangeLog	e130fcd32c009c5f7ad8b31d5736aed3e9f67c09
+++ ChangeLog	cef9fe8e359d630eb8f61133684772293748eade
@@ -1,3 +1,79 @@
+2006-02-27  Matt Johnston  <matt@ucc.asn.au>
+
+	* std_hooks.lua (ignore_file, dir_matches): add dir_matches helper
+	to avoid including ignored dirs such as '.svn', since dirs now
+	actually exist.
+
+2006-02-26  Nathaniel Smith  <njs@pobox.com>
+
+	* roster_merge.cc: Update notes on tests.
+
+2006-02-25  Nathaniel Smith  <njs@pobox.com>
+
+	* work.cc (attach_node): Apparently the tests depend on update
+	clobbering existing non-versioned files... and we don't have a
+	good solution _anyway_, so just go back to clobbering them.
+
+2006-02-25  Nathaniel Smith  <njs@pobox.com>
+
+	* cset.cc (root_dir_test, invalid_csets_test): Don't test that
+	root dir stuff fails.  Test that it works.
+
+2006-02-25  Nathaniel Smith  <njs@pobox.com>
+
+	* roster.cc (detach_node): Add missing invariant.
+
+2006-02-25  Nathaniel Smith  <njs@pobox.com>
+
+	* roster_merge.cc (roster_merge): Fix strange merge-created
+	problem...
+
+2006-02-25  Nathaniel Smith  <njs@pobox.com>
+
+	* work.cc (attach_node): This code should use path_exists, not
+	file_exists.
+
+2006-02-24  Timothy Brownawell  <tbrownaw@gmail.com>
+
+	* netcmd.cc: Ignore protocol version field on usher_cmd packets. It
+	should now be possible to use an usher to redirect connections based
+	on netsync version.
+	* netcmd.hh: Remove unused netcmd::get_version() and
+	unused/unimplemented netcmd::netcmd(u8 version).
+
+2006-02-24  Richard Levitte  <richard@levitte.org>
+
+	* commands.cc (ls_changed): I was a bit overly paranoid about the
+	possibilities with C++ and defined an explicit functor for the set
+	instead of relying on the automatic generation of less<file_path>.
+	Derek Scherger made me realise I was a bit overzealous, and this
+	change removes the explicit functor.
+
+2006-02-23  Matthew Gregan  <kinetik@orcon.net.nz>
+
+	* enumerator.hh: Another GCC 4.1 compile fix.
+
+	* tests/t_log_selectors.at: UnXFAIL.
+
+	* commands.cc (complete): Refactor selector completion to enable
+	return of single or sets of completed revisions.
+	(CMD(log)): Do something sensible with selectors that return
+	multiple revisions.
+
+	* tests/t_log_selectors.at: XFAILed test for bug #15877.
+
+	* testsuite.at: Add it.
+
+	* app_state.cc, app_state.hh, commands.cc, monotone.cc,
+	options.hh: Revert failed UI experiment: reenable logging merges
+	by default and rename --merges back to --no-merges.
+
+	* contrib/color-logs.{conf,sh}, contrib/monotone-notify.pl,
+	contrib/monotone.el, tests/t_log_nofiles_nomerges.at,
+	testsuite.at: Handle --no-merges.
+
+	* monotone.texi: Document --no-merges.
+
 2006-02-23  Matt Johnston  <matt@ucc.asn.au>

 	* enumerator.{cc,hh}: avoid transferring deltas on both sides of merge
@@ -15,6 +91,10 @@ 2006-02-21  Nathaniel Smith  <njs@pobox.

 2006-02-21  Nathaniel Smith  <njs@pobox.com>

+	* roster_merge.cc: Fixup after merge.
+
+2006-02-21  Nathaniel Smith  <njs@pobox.com>
+
 	* work.cc (detach_node): Check if we are passed the root dir, and
 	error out if so.

@@ -738,6 +818,17 @@ 2006-01-14  Nathaniel Smith  <njs@pobox.
 	* keys.cc (require_password): Adjust accordingly.
 	* cert.cc (priv_key_exists, load_key_pair): Likewise.

+2006-01-14  Christof Petig <christof@petig-baender.de>
+
+	* database.cc, database.h: binary transparent infrastructure,
+	store and retrieve former base64 encoded columns as BLOBs
+
+	* schema_migration.cc: unbase64 changed columns, change comment
+	on files.data which indicates a different database format
+
+	* schema.sql: change comment on file which indicates, that
+	files.data is no longer base64 encoded
+
 2006-01-14  Richard Levitte  <richard@levitte.org>

 	* configure.ac: Make sure there's an empty xgettext.opts
============================================================
--- app_state.cc	d6bd147ff69418001cff7cfd08e7ec18deca23ab
+++ app_state.cc	4990c26751504dded9cef9b1c6ffa66bef0d2b32
@@ -31,7 +31,7 @@ app_state::app_state()
 app_state::app_state()
   : branch_name(""), db(system_path()), keys(this), stdhooks(true),
     rcfiles(true), diffs(false),
-    merges(false), set_default(false), verbose(false), date_set(false),
+    no_merges(false), set_default(false), verbose(false), date_set(false),
     search_root("/"),
     depth(-1), last(-1), next(-1), diff_format(unified_diff), diff_args_provided(false),
     use_lca(false), execute(false), bind_address(""), bind_port(""),
============================================================
--- app_state.hh	7fb16348486ca507f22a99d6aca9ba2dc4a658c0
+++ app_state.hh	03afbc7ba543f0b26f8ddf70d97c05e872cdb36b
@@ -40,7 +40,7 @@ public:
   bool stdhooks;
   bool rcfiles;
   bool diffs;
-  bool merges;
+  bool no_merges;
   bool set_default;
   bool verbose;
   options_map options;
============================================================
--- commands.cc	8d15447176db8827209c2feeaaf0b0eaa7e2e1fd
+++ commands.cc	0fc12a37debd7db7812e2670afcb99d787f869e0
@@ -461,10 +461,11 @@ describe_revision(app_state & app, revis
   return description;
 }

+
 static void
 complete(app_state & app,
          string const & str,
-         revision_id & completion,
+         set<revision_id> & completion,
          bool must_exist=true)
 {
   // This copies the start of selectors::parse_selector().to avoid
@@ -475,10 +476,10 @@ complete(app_state & app,
   if (str.find_first_not_of(constants::legal_id_bytes) == string::npos
       && str.size() == constants::idlen)
     {
-      completion = revision_id(str);
+      completion.insert(revision_id(str));
       if (must_exist)
-        N(app.db.revision_exists(completion),
-          F("no such revision '%s'") % completion);
+        N(app.db.revision_exists(*completion.begin()),
+          F("no such revision '%s'") % *completion.begin());
       return;
     }

@@ -494,16 +495,36 @@ complete(app_state & app,

   N(completions.size() != 0,
     F("no match for selection '%s'") % str);
+
+  for (set<string>::const_iterator i = completions.begin();
+       i != completions.end(); ++i)
+    {
+      pair<set<revision_id>::const_iterator, bool> p = completion.insert(revision_id(*i));
+      P(F("expanded to '%s'\n") % *(p.first));
+    }
+}
+
+
+static void
+complete(app_state & app,
+         string const & str,
+         revision_id & completion,
+         bool must_exist=true)
+{
+  set<revision_id> completions;
+
+  complete(app, str, completions, must_exist);
+
   if (completions.size() > 1)
     {
       string err = (F("selection '%s' has multiple ambiguous expansions: \n") % str).str();
-      for (set<string>::const_iterator i = completions.begin();
+      for (set<revision_id>::const_iterator i = completions.begin();
            i != completions.end(); ++i)
-        err += (describe_revision(app, revision_id(*i)) + "\n");
+        err += (describe_revision(app, *i) + "\n");
       N(completions.size() == 1, i18n_format(err));
     }
-  completion = revision_id(*(completions.begin()));
-  P(F("expanded to '%s'\n") %  completion);
+
+  completion = *completions.begin();
 }


@@ -1243,6 +1264,23 @@ ALIAS(mv, rename)
 ALIAS(mv, rename)


+CMD(pivot_root, N_("workspace"), N_("NEW_ROOT PUT_OLD"),
+    N_("rename the root directory\n"
+       "after this command, the directory that currently has the name NEW_ROOT\n"
+       "will be the root directory, and the directory that is currently the root\n"
+       "directory will have name PUT_OLD.\n"
+       "Using --execute is strongly recommended."),
+    OPT_EXECUTE)
+{
+  if (args.size() != 2)
+    throw usage(name);
+
+  app.require_workspace();
+  file_path new_root = file_path_external(idx(args, 0));
+  file_path put_old = file_path_external(idx(args, 1));
+  perform_pivot_root(new_root, put_old, app);
+}
+
 // fload and fmerge are simple commands for debugging the line
 // merger.

@@ -1740,13 +1778,6 @@ ls_missing (app_state & app, vector<utf8
 }


-struct lt_file_path
-{
-  bool operator()(const file_path &fp1, const file_path &fp2) const
-  {
-    return fp1 < fp2;
-  }
-};
 static void
 ls_changed (app_state & app, vector<utf8> const & args)
 {
@@ -1754,7 +1785,7 @@ ls_changed (app_state & app, vector<utf8
   revision_id rid;
   roster_t old_roster, new_roster;
   data tmp;
-  std::set<file_path, lt_file_path> files;
+  std::set<file_path> files;

   app.require_workspace();
   get_working_revision_and_rosters(app, args, rs, old_roster, new_roster);
@@ -3410,7 +3441,11 @@ CMD(revert, N_("workspace"), N_("[PATH].
         }
       else
         {
-          mkdir_p(fp);
+          if (!directory_exists(fp))
+            {
+              P(F("recreating %s/") % fp);
+              mkdir_p(fp);
+            }
         }
     }

@@ -3542,7 +3577,7 @@ CMD(log, N_("informative"), N_("[FILE] .
 CMD(log, N_("informative"), N_("[FILE] ..."),
     N_("print history in reverse order (filtering by 'FILE'). If one or more\n"
     "revisions are given, use them as a starting point."),
-    OPT_LAST % OPT_NEXT % OPT_REVISION % OPT_BRIEF % OPT_DIFFS % OPT_MERGES %
+    OPT_LAST % OPT_NEXT % OPT_REVISION % OPT_BRIEF % OPT_DIFFS % OPT_NO_MERGES %
     OPT_NO_FILES)
 {
   if (app.revision_selectors.size() == 0)
@@ -3563,11 +3598,11 @@ CMD(log, N_("informative"), N_("[FILE] .
       for (std::vector<utf8>::const_iterator i = app.revision_selectors.begin();
            i != app.revision_selectors.end(); i++)
         {
-          revision_id rid;
-          complete(app, (*i)(), rid);
-          frontier.insert(rid);
+          set<revision_id> rids;
+          complete(app, (*i)(), rids);
+          frontier.insert(rids.begin(), rids.end());
           if (i == app.revision_selectors.begin())
-            first_rid = rid;
+            first_rid = *rids.begin();
         }
     }

@@ -3690,7 +3725,7 @@ CMD(log, N_("informative"), N_("[FILE] .
                    inserter(next_frontier, next_frontier.end()));
             }

-          if (!app.merges && rev.is_merge_node())
+          if (app.no_merges && rev.is_merge_node())
             print_this = false;

           if (print_this)
============================================================
--- contrib/color-logs.conf	ffe3e7270a2952c508db2f2d9c3df82e05c859f4
+++ contrib/color-logs.conf	87e8ceac1e0a8d173e2a0da01e659d048824e9ed
@@ -1,6 +1,6 @@
 #
 # this is a colorization config for reading the output of "monotone log"
-# or "monotone diff", or best of all "monotone log --diffs".
+# or "monotone diff", or best of all "monotone log --no-merges --diffs".
 #
 # use with the "colorize" script in this dir, or color-logs.sh
 #
============================================================
--- contrib/color-logs.sh	90a0de6aa87b7f4b39f7947fd1d359a3cc936385
+++ contrib/color-logs.sh	4e04daeee4ee8ce8bf6ce45f074ba16faf50ccf9
@@ -1,5 +1,5 @@
 #!/bin/sh

-./monotone log --diffs $@    \
+./monotone log --diffs --no-merges $@    \
   | ./contrib/colorize -c contrib/color-logs.conf  \
   | less -r -p -----------------------------------------------------------------
============================================================
--- contrib/monotone-notify.pl	aa93d7f9f601b51d607bd4d779e472067c41d5a2
+++ contrib/monotone-notify.pl	d7f551f0f332f56785aaa59e99ade7400c8da84a
@@ -268,7 +268,7 @@ if ($mail || $debug) {
     foreach my $revision (keys %revisions) {
 	$revision_data{$revision} =
 	    [ map { chomp; $_ }
-	      my_backtick("$monotone$database log --last=1 --merges --revision=$revision") ];
+	      my_backtick("$monotone$database log --last=1 --revision=$revision") ];
 	my $date = (split(' ', (grep(/^Date:/, @{$revision_data{$revision}}))[0]))[1];

 	if (defined $before && $date ge $before) {
@@ -582,7 +582,7 @@ sub revision_is_in_branch
     if (!defined $$revision_data{$revision}) {
 	$$revision_data{$revision} =
 	    [ map { chomp; $_ }
-	      my_backtick("$monotone$database log --last=1 --merges --revision=$revision") ];
+	      my_backtick("$monotone$database log --last=1 --revision=$revision") ];
     }

     map {
============================================================
--- contrib/monotone.el	3dc1c23d499d1aa015ed7f145416a241a55f054f
+++ contrib/monotone.el	9e1a1ec218b98cd38c6126c48f00a1d585e65a13
@@ -686,7 +686,7 @@ the buffer if not global."
   (when (eq 'tree (monotone-arg-decode arg))
     (error "monotone subtree log is busted"))
   ;;
-  (let ((cmds (list "log"))
+  (let ((cmds (list "log" "--no-merges"))
         (depth monotone-log-depth))
     (when (and (numberp depth) (< 0 depth))
       (setq cmds (append cmds (list (format "--last=%d" depth)))))
============================================================
--- cset.cc	a8497f9a82330f842cda2f242e97ef48e3a6b9fb
+++ cset.cc	5fdac8f01ad19e4a427e40aa4245026b534422f7
@@ -1072,56 +1072,76 @@ invalid_csets_test()
     BOOST_CHECK_THROW(cs.apply_to(tree), std::logic_error);
   }
   {
-    L(FL("TEST: can't rename root (for now)"));
+    L(FL("TEST: can't delete non-empty directory"));
     setup_roster(r, f1, nis);
     cset cs; MM(cs);
-    split_path sp1, sp2;
-    cs.dirs_added.insert(root);
-    cs.nodes_renamed.insert(std::make_pair(root, baz));
+    cs.nodes_deleted.insert(foo);
     BOOST_CHECK_THROW(cs.apply_to(tree), std::logic_error);
   }
   {
-    L(FL("TEST: can't delete non-empty directory"));
-    setup_roster(r, f1, nis);
+    L(FL("TEST: attach node with no root directory present"));
+    // for this test, make sure original roster has no contents
+    r = roster_t();
     cset cs; MM(cs);
-    cs.nodes_deleted.insert(foo);
+    split_path sp;
+    file_path_internal("blah/blah/blah").split(sp);
+    cs.dirs_added.insert(sp);
     BOOST_CHECK_THROW(cs.apply_to(tree), std::logic_error);
   }
   {
-    L(FL("TEST: can't delete root"));
-    // for this test, make sure root has no contents
-    r = roster_t();
+    L(FL("TEST: can't move a directory underneath itself"));
+    setup_roster(r, f1, nis);
     cset cs; MM(cs);
-    cs.nodes_deleted.insert(root);
+    split_path foo_blah;
+    file_path_internal("foo/blah").split(foo_blah);
+    cs.nodes_renamed.insert(std::make_pair(foo, foo_blah));
     BOOST_CHECK_THROW(cs.apply_to(tree), std::logic_error);
   }
+}
+
+void
+root_dir_test()
+{
+  temp_node_id_source nis;
+  roster_t r;
+  MM(r);
+  editable_roster_base tree(r, nis);
+
+  file_id f1(std::string("0000000000000000000000000000000000000001"));
+
+  split_path root, baz;
+  file_path().split(root);
+  file_path_internal("baz").split(baz);
+
   {
-    L(FL("TEST: can't delete and replace root"));
-    // for this test, make sure root has no contents
-    r = roster_t();
+    L(FL("TEST: can rename root"));
+    setup_roster(r, f1, nis);
     cset cs; MM(cs);
-    cs.nodes_deleted.insert(root);
+    split_path sp1, sp2;
     cs.dirs_added.insert(root);
-    BOOST_CHECK_THROW(cs.apply_to(tree), std::logic_error);
+    cs.nodes_renamed.insert(std::make_pair(root, baz));
+    cs.apply_to(tree);
+    r.check_sane(true);
   }
   {
-    L(FL("TEST: attach node with no root directory present"));
+    L(FL("TEST: can delete root (but it makes us insane)"));
     // for this test, make sure root has no contents
     r = roster_t();
+    r.attach_node(r.create_dir_node(nis), root);
     cset cs; MM(cs);
-    split_path sp;
-    file_path_internal("blah/blah/blah").split(sp);
-    cs.dirs_added.insert(sp);
-    BOOST_CHECK_THROW(cs.apply_to(tree), std::logic_error);
+    cs.nodes_deleted.insert(root);
+    cs.apply_to(tree);
+    BOOST_CHECK_THROW(r.check_sane(true), std::logic_error);
   }
   {
-    L(FL("TEST: can't move a directory underneath itself"));
-    setup_roster(r, f1, nis);
+    L(FL("TEST: can delete and replace root"));
+    r = roster_t();
+    r.attach_node(r.create_dir_node(nis), root);
     cset cs; MM(cs);
-    split_path foo_blah;
-    file_path_internal("foo/blah").split(foo_blah);
-    cs.nodes_renamed.insert(std::make_pair(foo, foo_blah));
-    BOOST_CHECK_THROW(cs.apply_to(tree), std::logic_error);
+    cs.nodes_deleted.insert(root);
+    cs.dirs_added.insert(root);
+    cs.apply_to(tree);
+    r.check_sane(true);
   }
 }

@@ -1132,6 +1152,7 @@ add_cset_tests(test_suite * suite)
   suite->add(BOOST_TEST_CASE(&basic_csets_test));
   suite->add(BOOST_TEST_CASE(&invalid_csets_test));
   suite->add(BOOST_TEST_CASE(&cset_written_test));
+  suite->add(BOOST_TEST_CASE(&root_dir_test));
 }

 #endif // BUILD_UNIT_TESTS
============================================================
--- database.cc	70d4d8f0c43de5e18bb023fc2c87805c0cdfa659
+++ database.cc	61bac503d11d0d68da007b53ec4a7f31e0453cdc
@@ -121,12 +121,6 @@ struct query
   std::string sql_cmd;
 };

-extern "C" {
-// some wrappers to ease migration
-  const char *sqlite3_value_text_s(sqlite3_value *v);
-  const char *sqlite3_column_text_s(sqlite3_stmt*, int col);
-}
-
 database::database(system_path const & fn) :
   filename(fn),
   // nb. update this if you change the schema. unfortunately we are not
@@ -205,43 +199,18 @@ database::check_format()
     }
 }

-// sqlite3_value_text gives a const unsigned char * but most of the time
-// we need a signed char
-const char *
-sqlite3_value_text_s(sqlite3_value *v)
-{
-  return (const char *)(sqlite3_value_text(v));
-}
-
-const char *
-sqlite3_column_text_s(sqlite3_stmt *stmt, int col)
-{
-  return (const char *)(sqlite3_column_text(stmt, col));
-}
-
-static void
-sqlite3_unbase64_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
-{
-  if (nargs != 1)
-    {
-      sqlite3_result_error(f, "need exactly 1 arg to unbase64()", -1);
-      return;
-    }
-  data decoded;
-  decode_base64(base64<data>(string(sqlite3_value_text_s(args[0]))), decoded);
-  sqlite3_result_blob(f, decoded().c_str(), decoded().size(), SQLITE_TRANSIENT);
-}
-
 static void
-sqlite3_unpack_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
+sqlite3_gunzip_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
 {
   if (nargs != 1)
     {
-      sqlite3_result_error(f, "need exactly 1 arg to unpack()", -1);
+      sqlite3_result_error(f, "need exactly 1 arg to gunzip()", -1);
       return;
     }
   data unpacked;
-  unpack(base64< gzip<data> >(string(sqlite3_value_text_s(args[0]))), unpacked);
+  const char *val = (const char*) sqlite3_value_blob(args[0]);
+  int bytes = sqlite3_value_bytes(args[0]);
+  decode_gzip(gzip<data>(std::string(val,val+bytes)), unpacked);
   sqlite3_result_blob(f, unpacked().c_str(), unpacked().size(), SQLITE_TRANSIENT);
 }

@@ -363,42 +332,49 @@ dump_request
 struct
 dump_request
 {
-  dump_request() {};
+  dump_request() : sql(), out() {};
   struct sqlite3 *sql;
-  string table_name;
   ostream *out;
 };

-static int
-dump_row_cb(void *data, int n, char **vals, char **cols)
+static void
+dump_row(std::ostream &out, sqlite3_stmt *stmt, std::string const& table_name)
 {
-  dump_request *dump = reinterpret_cast<dump_request *>(data);
-  I(dump != NULL);
-  I(vals != NULL);
-  I(dump->out != NULL);
-  *(dump->out) << boost::format("INSERT INTO %s VALUES(") % dump->table_name;
-  for (int i = 0; i < n; ++i)
+  out << boost::format("INSERT INTO %s VALUES(") % table_name;
+  unsigned n = sqlite3_data_count(stmt);
+  for (unsigned i = 0; i < n; ++i)
     {
       if (i != 0)
-        *(dump->out) << ',';
+        out << ',';

-      if (vals[i] == NULL)
-        *(dump->out) << "NULL";
-      else
+      if (sqlite3_column_type(stmt, i) == SQLITE_BLOB)
         {
-          *(dump->out) << "'";
-          for (char *cp = vals[i]; *cp; ++cp)
+          out << "X'";
+          const char *val = (const char*) sqlite3_column_blob(stmt, i);
+          int bytes = sqlite3_column_bytes(stmt, i);
+          out << encode_hexenc(std::string(val,val+bytes));
+          out << "'";
+        }
+      else
+        {
+          const unsigned char *val = sqlite3_column_text(stmt, i);
+          if (val == NULL)
+            out << "NULL";
+          else
             {
-              if (*cp == '\'')
-                *(dump->out) << "''";
-              else
-                *(dump->out) << *cp;
+              out << "'";
+              for (const unsigned char *cp = val; *cp; ++cp)
+                {
+                  if (*cp == '\'')
+                    out << "''";
+                  else
+                    out << *cp;
+                }
+              out << "'";
             }
-          *(dump->out) << "'";
         }
     }
-  *(dump->out) << ");\n";
-  return 0;
+  out << ");\n";
 }

 static int
@@ -414,10 +390,24 @@ dump_table_cb(void *data, int n, char **
   I(n == 3);
   I(string(vals[1]) == "table");
   *(dump->out) << vals[2] << ";\n";
-  dump->table_name = string(vals[0]);
-  string query = "SELECT * FROM " + string(vals[0]);
-  sqlite3_exec(dump->sql, query.c_str(), dump_row_cb, data, NULL);
+  string table_name(vals[0]);
+  string query = "SELECT * FROM " + table_name;
+  sqlite3_stmt *stmt = 0;
+  sqlite3_prepare(dump->sql, query.c_str(), -1, &stmt, NULL);
   assert_sqlite3_ok(dump->sql);
+
+  int stepresult = SQLITE_DONE;
+  do
+    {
+      stepresult = sqlite3_step(stmt);
+      I(stepresult == SQLITE_DONE || stepresult == SQLITE_ROW);
+      if (stepresult == SQLITE_ROW)
+        dump_row(*(dump->out), stmt, table_name);
+    }
+  while (stepresult == SQLITE_ROW);
+
+  sqlite3_finalize(stmt);
+  assert_sqlite3_ok(dump->sql);
   return 0;
 }

@@ -561,13 +551,14 @@ database::info(ostream & out)
     % count("revision_ancestry")
     % count("revision_certs")
     // bytes
-    % SPACE_USAGE("rosters", "id || data")
-    % SPACE_USAGE("roster_deltas", "id || base || delta")
-    % SPACE_USAGE("files", "id || data")
-    % SPACE_USAGE("file_deltas", "id || base || delta")
-    % SPACE_USAGE("revisions", "id || data")
-    % SPACE_USAGE("revision_ancestry", "parent || child")
-    % SPACE_USAGE("revision_certs", "hash || id || name || value || keypair || signature")
+    % SPACE_USAGE("rosters", "length(id) + length(data)")
+    % SPACE_USAGE("roster_deltas", "length(id) + length(base) + length(delta)")
+    % SPACE_USAGE("files", "length(id) + length(data)")
+    % SPACE_USAGE("file_deltas", "length(id) + length(base) + length(delta)")
+    % SPACE_USAGE("revisions", "length(id) + length(data)")
+    % SPACE_USAGE("revision_ancestry", "length(parent) + length(child)")
+    % SPACE_USAGE("revision_certs", "length(hash) + length(id) + length(name)"
+                  " + length(value) + length(keypair) + length(signature)")
     % total;

 #undef SPACE_USAGE
@@ -732,9 +723,10 @@ database::fetch(results & res,
       vector<string> row;
       for (int col = 0; col < ncol; col++)
         {
-          const char * value = sqlite3_column_text_s(i->second.stmt(), col);
+          const char * value = (const char*)sqlite3_column_blob(i->second.stmt(), col);
+          int bytes = sqlite3_column_bytes(i->second.stmt(), col);
           E(value, F("null result in query: %s\n") % query.sql_cmd);
-          row.push_back(value);
+          row.push_back(std::string(value, value + bytes));
           //L(FL("row %d col %d value='%s'\n") % nrow % col % value);
         }
       res.push_back(row);
@@ -843,12 +835,12 @@ unsigned long
 }

 unsigned long
-database::space_usage(string const & table, string const & concatenated_columns)
+database::space_usage(string const & table, string const & rowspace)
 {
   results res;
   // COALESCE is required since SUM({empty set}) is NULL.
   // the sqlite docs for SUM suggest this as a workaround
-  query q("SELECT COALESCE(SUM(LENGTH(" + concatenated_columns + ")), 0) FROM " + table);
+  query q("SELECT COALESCE(SUM(" + rowspace + "), 0) FROM " + table);
   fetch(res, one_col, one_row, q);
   return lexical_cast<unsigned long>(res[0][0]);
 }
@@ -876,9 +868,9 @@ database::get(hexenc<id> const & ident,
   fetch(res, one_col, one_row, q % text(ident()));

   // consistency check
-  base64<gzip<data> > rdata(res[0][0]);
+  gzip<data> rdata(res[0][0]);
   data rdata_unpacked;
-  unpack(rdata, rdata_unpacked);
+  decode_gzip(rdata,rdata_unpacked);

   hexenc<id> tid;
   calculate_ident(rdata_unpacked, tid);
@@ -899,8 +891,8 @@ database::get_delta(hexenc<id> const & i
   query q("SELECT delta FROM " + table + " WHERE id = ? AND base = ?");
   fetch(res, one_col, one_row, q % text(ident()) % text(base()));

-  base64<gzip<delta> > del_packed = res[0][0];
-  unpack(del_packed, del);
+  gzip<delta> del_packed(res[0][0]);
+  decode_gzip(del_packed, del);
 }

 void
@@ -916,15 +908,14 @@ database::put(hexenc<id> const & ident,
   MM(tid);
   I(tid == ident);

-  base64<gzip<data> > dat_packed;
-  pack(dat, dat_packed);
-
+  gzip<data> dat_packed;
+  encode_gzip(dat, dat_packed);
   uint32_t dat_size = dat_packed().size();

   string insert = "INSERT INTO " + table + " VALUES(?, ?, ?)";
   execute(query(insert)
           % text(ident())
-          % text(dat_packed())
+          % blob(dat_packed()));
           % integer(dat_size));
 }
 void
@@ -938,8 +929,8 @@ database::put_delta(hexenc<id> const & i
   I(!null_id(ident));
   I(!null_id(base));

-  base64<gzip<delta> > del_packed;
-  pack(del, del_packed);
+  gzip<delta> del_packed;
+  encode_gzip(del, del_packed);

   uint32_t del_size = del_packed().size();

@@ -947,7 +938,7 @@ database::put_delta(hexenc<id> const & i
   execute(query(insert)
           % text(ident())
           % text(base())
-          % text(del_packed())
+          % blob(del_packed()));
           % integer(parent_distance + 1)
           % integer(parent_size + del_size)
           % integer(del_size));
@@ -1584,10 +1575,9 @@ database::get_revision(revision_id const
         query("SELECT data FROM revisions WHERE id = ?")
         % text(id.inner()()));

-  base64<gzip<data> > rdat_packed;
-  rdat_packed = base64<gzip<data> >(res[0][0]);
+  gzip<data> gzdata(res[0][0]);
   data rdat;
-  unpack(rdat_packed, rdat);
+  decode_gzip(gzdata,rdat);

   // verify that we got a revision with the right id
   {
@@ -1755,14 +1745,14 @@ database::put_revision(revision_id const

   // Phase 3: Write the revision data

-  base64<gzip<data> > d_packed;
-  pack(d.inner(), d_packed);
+  gzip<data> d_packed;
+  encode_gzip(d.inner(), d_packed);

   uint32_t d_size = d_packed().size();

   execute(query("INSERT INTO revisions VALUES(?, ?, ?)")
           % text(new_id.inner()())
-          % text(d_packed())
+          % blob(d_packed()));
           % integer(d_size));

   for (edge_map::const_iterator e = rev.edges.begin();
@@ -1862,24 +1852,20 @@ database::delete_branch_named(cert_value
 void
 database::delete_branch_named(cert_value const & branch)
 {
-  base64<cert_value> encoded;
-  encode_base64(branch, encoded);
   L(FL("Deleting all references to branch %s\n") % branch);
   execute(query("DELETE FROM revision_certs WHERE name='branch' AND value =?")
-          % text(encoded()));
+          % blob(branch()));
   execute(query("DELETE FROM branch_epochs WHERE branch=?")
-          % text(encoded()));
+          % blob(branch()));
 }

 /// Deletes all certs referring to a particular tag.
 void
 database::delete_tag_named(cert_value const & tag)
 {
-  base64<cert_value> encoded;
-  encode_base64(tag, encoded);
   L(FL("Deleting all references to tag %s\n") % tag);
   execute(query("DELETE FROM revision_certs WHERE name='tag' AND value =?")
-          % text(encoded()));
+          % blob(tag()));
 }

 // crypto key management
@@ -1955,7 +1941,7 @@ database::get_pubkey(hexenc<id> const &
         query("SELECT id, keydata FROM public_keys WHERE hash = ?")
         % text(hash()));
   id = res[0][0];
-  pub_encoded = res[0][1];
+  encode_base64(rsa_pub_key(res[0][1]), pub_encoded);
 }

 void
@@ -1966,7 +1952,7 @@ database::get_key(rsa_keypair_id const &
   fetch(res, one_col, one_row,
         query("SELECT keydata FROM public_keys WHERE id = ?")
         % text(pub_id()));
-  pub_encoded = res[0][0];
+  encode_base64(rsa_pub_key(res[0][0]), pub_encoded);
 }

 void
@@ -1978,10 +1964,12 @@ database::put_key(rsa_keypair_id const &
   I(!public_key_exists(thash));
   E(!public_key_exists(pub_id),
     F("another key with name '%s' already exists") % pub_id);
+  rsa_pub_key pub_key;
+  decode_base64(pub_encoded, pub_key);
   execute(query("INSERT INTO public_keys VALUES(?, ?, ?)")
           % text(thash())
           % text(pub_id())
-          % text(pub_encoded()));
+          % blob(pub_key()));
 }

 void
@@ -1998,6 +1986,10 @@ database::cert_exists(cert const & t,
                       string const & table)
 {
   results res;
+  cert_value value;
+  decode_base64(t.value, value);
+  rsa_sha1_signature sig;
+  decode_base64(t.sig, sig);
   query q = query("SELECT id FROM " + table + " WHERE id = ? "
                   "AND name = ? "
                   "AND value = ? "
@@ -2005,10 +1997,10 @@ database::cert_exists(cert const & t,
                   "AND signature = ?")
     % text(t.ident())
     % text(t.name())
-    % text(t.value())
+    % blob(value())
     % text(t.key())
-    % text(t.sig());
-
+    % blob(sig());
+
   fetch(res, 1, any_rows, q);

   I(res.size() == 0 || res.size() == 1);
@@ -2021,6 +2013,10 @@ database::put_cert(cert const & t,
 {
   hexenc<id> thash;
   cert_hash_code(t, thash);
+  cert_value value;
+  decode_base64(t.value, value);
+  rsa_sha1_signature sig;
+  decode_base64(t.sig, sig);

   string insert = "INSERT INTO " + table + " VALUES(?, ?, ?, ?, ?, ?)";

@@ -2028,9 +2024,9 @@ database::put_cert(cert const & t,
           % text(thash())
           % text(t.ident())
           % text(t.name())
-          % text(t.value())
+          % blob(value())
           % text(t.key())
-          % text(t.sig()));
+          % blob(sig()));
 }

 void
@@ -2041,11 +2037,15 @@ database::results_to_certs(results const
   for (size_t i = 0; i < res.size(); ++i)
     {
       cert t;
+      base64<cert_value> value;
+      encode_base64(cert_value(res[i][2]), value);
+      base64<rsa_sha1_signature> sig;
+      encode_base64(rsa_sha1_signature(res[i][4]), sig);
       t = cert(hexenc<id>(res[i][0]),
               cert_name(res[i][1]),
-              base64<cert_value>(res[i][2]),
+              value,
               rsa_keypair_id(res[i][3]),
-              base64<rsa_sha1_signature>(res[i][4]));
+              sig);
       certs.push_back(t);
     }
 }
@@ -2054,14 +2054,10 @@ database::install_functions(app_state *
 database::install_functions(app_state * app)
 {
   // register any functions we're going to use
-  I(sqlite3_create_function(sql(), "unbase64", -1,
+  I(sqlite3_create_function(sql(), "gunzip", -1,
                            SQLITE_UTF8, NULL,
-                           &sqlite3_unbase64_fn,
+                           &sqlite3_gunzip_fn,
                            NULL, NULL) == 0);
-  I(sqlite3_create_function(sql(), "unpack", -1,
-                           SQLITE_UTF8, NULL,
-                           &sqlite3_unpack_fn,
-                           NULL, NULL) == 0);
 }

 void
@@ -2150,9 +2146,11 @@ database::get_certs(cert_name const & na
   query q("SELECT id, name, value, keypair, signature FROM " + table +
           " WHERE name = ? AND value = ?");

-  fetch(res, 5, any_rows,
+  cert_value binvalue;
+  decode_base64(val, binvalue);
+  fetch(res, 5, any_rows,
         q % text(name())
-          % text(val()));
+          % blob(binvalue()));
   results_to_certs(res, certs);
 }

@@ -2168,10 +2166,12 @@ database::get_certs(hexenc<id> const & i
   query q("SELECT id, name, value, keypair, signature FROM " + table +
           " WHERE id = ? AND name = ? AND value = ?");

+  cert_value binvalue;
+  decode_base64(value, binvalue);
   fetch(res, 5, any_rows,
         q % text(ident())
           % text(name())
-          % text(value()));
+          % blob(binvalue()));
   results_to_certs(res, certs);
 }

@@ -2488,7 +2488,7 @@ void database::complete(selector_type ty
                       spot++;
                       certvalue = i->second.substr(spot);
                       lim += "SELECT id FROM revision_certs ";
-                      lim += (boost::format("WHERE name='%s' AND unbase64(value) glob '%s'")
+                      lim += (boost::format("WHERE name='%s' AND CAST(value AS TEXT) glob '%s'")
                               % certname % certvalue).str();
                     }
                   else
@@ -2507,7 +2507,7 @@ void database::complete(selector_type ty
                       % author_cert_name
                       % tag_cert_name
                       % branch_cert_name).str();
-              lim += (boost::format(" AND unbase64(value) glob '*%s*'")
+              lim += (boost::format(" AND CAST(value AS TEXT) glob '*%s*'")
                       % i->second).str();
             }
           else if (i->first == selectors::sel_head)
@@ -2521,15 +2521,13 @@ void database::complete(selector_type ty
                 }
               else
                 {
-                  string subquery = (boost::format("SELECT DISTINCT value FROM revision_certs WHERE name='%s' and unbase64(value) glob '%s'")
+                  string subquery = (boost::format("SELECT DISTINCT value FROM revision_certs WHERE name='%s' and CAST(value AS TEXT) glob '%s'")
                                      % branch_cert_name % i->second).str();
                   results res;
                   fetch(res, one_col, any_rows, query(subquery));
                   for (size_t i = 0; i < res.size(); ++i)
                     {
-                      base64<data> row_encoded(res[i][0]);
-                      data row_decoded;
-                      decode_base64(row_encoded, row_decoded);
+                      data row_decoded(res[i][0]);
                       branch_names.push_back(row_decoded());
                     }
                 }
@@ -2568,8 +2566,7 @@ void database::complete(selector_type ty
               if ((i->first == selectors::sel_branch) && (i->second.size() == 0))
                 {
                   __app->require_workspace("the empty branch selector b: refers to the current branch");
-                  // FIXME: why do we have to glob on the unbase64(value), rather than being able to use == ?
-                  lim += (boost::format("SELECT id FROM revision_certs WHERE name='%s' AND unbase64(value) glob '%s'")
+                  lim += (boost::format("SELECT id FROM revision_certs WHERE name='%s' AND CAST(value AS TEXT) glob '%s'")
                           % branch_cert_name % __app->branch_name).str();
                   L(FL("limiting to current branch '%s'\n") % __app->branch_name);
                 }
@@ -2579,13 +2576,13 @@ void database::complete(selector_type ty
                   switch (i->first)
                     {
                     case selectors::sel_earlier:
-                      lim += (boost::format("unbase64(value) <= X'%s'") % encode_hexenc(i->second)).str();
+                      lim += (boost::format("value <= X'%s'") % encode_hexenc(i->second)).str();
                       break;
                     case selectors::sel_later:
-                      lim += (boost::format("unbase64(value) > X'%s'") % encode_hexenc(i->second)).str();
+                      lim += (boost::format("value > X'%s'") % encode_hexenc(i->second)).str();
                       break;
                     default:
-                      lim += (boost::format("unbase64(value) glob '%s%s%s'")
+                      lim += (boost::format("CAST(value AS TEXT) glob '%s%s%s'")
                               % prefix % i->second % suffix).str();
                       break;
                     }
@@ -2626,7 +2623,7 @@ void database::complete(selector_type ty
             (boost::format(" (name='%s')") % certname).str();
         }

-      query_str += (boost::format(" AND (unbase64(value) GLOB '%s%s%s')")
+      query_str += (boost::format(" AND (CAST(value AS TEXT) GLOB '%s%s%s')")
                 % prefix % partial % suffix).str();
       query_str += (boost::format(" AND (id IN %s)") % lim).str();
     }
@@ -2639,9 +2636,7 @@ void database::complete(selector_type ty
         completions.insert(res[i][0]);
       else
         {
-          base64<data> row_encoded(res[i][0]);
-          data row_decoded;
-          decode_base64(row_encoded, row_decoded);
+          data row_decoded(res[i][0]);
           completions.insert(row_decoded());
         }
     }
@@ -2657,9 +2652,7 @@ database::get_epochs(std::map<cert_value
   fetch(res, 2, any_rows, query("SELECT branch, epoch FROM branch_epochs"));
   for (results::const_iterator i = res.begin(); i != res.end(); ++i)
     {
-      base64<cert_value> encoded(idx(*i, 0));
-      cert_value decoded;
-      decode_base64(encoded, decoded);
+      cert_value decoded(idx(*i, 0));
       I(epochs.find(decoded) == epochs.end());
       epochs.insert(std::make_pair(decoded, epoch_data(idx(*i, 1))));
     }
@@ -2676,8 +2669,7 @@ database::get_epoch(epoch_id const & eid
         " WHERE hash = ?")
         % text(eid.inner()()));
   I(res.size() == 1);
-  base64<cert_value> encoded(idx(idx(res, 0), 0));
-  decode_base64(encoded, branch);
+  branch = cert_value(idx(idx(res, 0), 0));
   epo = epoch_data(idx(idx(res, 0), 1));
 }

@@ -2696,23 +2688,19 @@ database::set_epoch(cert_value const & b
 database::set_epoch(cert_value const & branch, epoch_data const & epo)
 {
   epoch_id eid;
-  base64<cert_value> encoded;
-  encode_base64(branch, encoded);
   epoch_hash_code(branch, epo, eid);
   I(epo.inner()().size() == constants::epochlen);
   execute(query("INSERT OR REPLACE INTO branch_epochs VALUES(?, ?, ?)")
           % text(eid.inner()())
-          % text(encoded())
+          % blob(branch())
           % text(epo.inner()()));
 }

 void
 database::clear_epoch(cert_value const & branch)
 {
-  base64<cert_value> encoded;
-  encode_base64(branch, encoded);
   execute(query("DELETE FROM branch_epochs WHERE branch = ?")
-          % text(encoded()));
+          % blob(branch()));
 }

 // vars
@@ -2726,12 +2714,8 @@ database::get_vars(std::map<var_key, var
   for (results::const_iterator i = res.begin(); i != res.end(); ++i)
     {
       var_domain domain(idx(*i, 0));
-      base64<var_name> name_encoded(idx(*i, 1));
-      var_name name;
-      decode_base64(name_encoded, name);
-      base64<var_value> value_encoded(idx(*i, 2));
-      var_value value;
-      decode_base64(value_encoded, value);
+      var_name name(idx(*i, 1));
+      var_value value(idx(*i, 2));
       I(vars.find(std::make_pair(domain, name)) == vars.end());
       vars.insert(std::make_pair(std::make_pair(domain, name), value));
     }
@@ -2761,24 +2745,18 @@ database::set_var(var_key const & key, v
 void
 database::set_var(var_key const & key, var_value const & value)
 {
-  base64<var_name> name_encoded;
-  encode_base64(key.second, name_encoded);
-  base64<var_value> value_encoded;
-  encode_base64(value, value_encoded);
   execute(query("INSERT OR REPLACE INTO db_vars VALUES(?, ?, ?)")
           % text(key.first())
-          % text(name_encoded())
-          % text(value_encoded()));
+          % blob(key.second())
+          % blob(value()));
 }

 void
 database::clear_var(var_key const & key)
 {
-  base64<var_name> name_encoded;
-  encode_base64(key.second, name_encoded);
   execute(query("DELETE FROM db_vars WHERE domain = ? AND name = ?")
           % text(key.first())
-          % text(name_encoded()));
+          % blob(key.second()));
 }

 // branches
@@ -2792,10 +2770,7 @@ database::get_branches(vector<string> &
     fetch(res, one_col, any_rows, q % text(cert_name));
     for (size_t i = 0; i < res.size(); ++i)
       {
-        base64<data> row_encoded(res[i][0]);
-        data name;
-        decode_base64(row_encoded, name);
-        names.push_back(name());
+        names.push_back(res[i][0]);
       }
 }

@@ -2906,7 +2881,7 @@ database::put_roster(revision_id const &
       get_version(old_id, old_data, data_table, delta_table);
       delta del;
       diff(old_data, new_data, del);
-      put_version(old_id, new_id, del, new_data, data_table, delta_table);
+      put_delta(new_id, old_id, del, delta_table);
       delta_written = true;
       break;
     }
============================================================
--- file_io.cc	3214c2cad3ec630d8466926bd4bae693de9184a0
+++ file_io.cc	3ca8050b61b62ef9010b0ebfda755a0af9269104
@@ -347,15 +347,15 @@ read_localized_data(file_path const & pa
   dat = tmp2;
 }

-void read_directory(system_path const & path,
+void read_directory(any_path const & path,
                     std::vector<utf8> & files,
                     std::vector<utf8> & dirs)
 {
   files.clear();
   dirs.clear();
   fs::directory_iterator ei;
-  for(fs::directory_iterator di(path.as_external());
-      di != ei; ++di)
+  for (fs::directory_iterator di(system_path(path).as_external());
+       di != ei; ++di)
     {
       fs::path entry = *di;
       if (!fs::exists(entry)
@@ -363,6 +363,8 @@ void read_directory(system_path const &
           || di->string() == "..")
         continue;

+      // FIXME: BUG: this screws up charsets (assumes blindly that the fs is
+      // utf8)
       if (fs::is_directory(entry))
         dirs.push_back(utf8(entry.leaf()));
       else
@@ -484,8 +486,6 @@ write_data(system_path const & path,
 {
   write_data_impl(path, data, tmpdir / (boost::format("data.tmp.%d") %
                                              get_process_id()).str());
-
-
 }

 tree_walker::~tree_walker() {}
============================================================
--- file_io.hh	2082dc3c8480400f32fcbe1c4d881d7512de2204
+++ file_io.hh	362691f10f63d93e7cf83f299fe5aa0b4dcbee17
@@ -72,7 +72,7 @@ void read_localized_data(file_path const
                          data & dat,
                          lua_hooks & lua);

-void read_directory(system_path const & path,
+void read_directory(any_path const & path,
                     std::vector<utf8> & files,
                     std::vector<utf8> & dirs);

@@ -101,6 +101,7 @@ public:
 class tree_walker
 {
 public:
+  // returns true if the directory should be descended into
   virtual void visit_dir(file_path const & path);
   virtual void visit_file(file_path const & path) = 0;
   virtual ~tree_walker();
============================================================
--- monotone.cc	6626e22d6ef4d9c8d3380a2f498599dd57bc6d3d
+++ monotone.cc	29890d9e90e828fa96d845cb881bb024a8267d99
@@ -60,7 +60,7 @@ struct poptOption coptions[] =
     {"pid-file", 0, POPT_ARG_STRING, &argstr, OPT_PIDFILE, gettext_noop("record process id of server"), NULL},
     {"brief", 0, POPT_ARG_NONE, NULL, OPT_BRIEF, gettext_noop("print a brief version of the normal output"), NULL},
     {"diffs", 0, POPT_ARG_NONE, NULL, OPT_DIFFS, gettext_noop("print diffs along with logs"), NULL},
-    {"merges", 0, POPT_ARG_NONE, NULL, OPT_MERGES, gettext_noop("include merges when printing logs"), NULL},
+    {"no-merges", 0, POPT_ARG_NONE, NULL, OPT_NO_MERGES, gettext_noop("exclude merges when printing logs"), NULL},
     {"set-default", 0, POPT_ARG_NONE, NULL, OPT_SET_DEFAULT, gettext_noop("use the current arguments as the future default"), NULL},
     {"exclude", 0, POPT_ARG_STRING, &argstr, OPT_EXCLUDE, gettext_noop("leave out anything described by its argument"), NULL},
     {"unified", 0, POPT_ARG_NONE, NULL, OPT_UNIFIED_DIFF, gettext_noop("use unified diff format"), NULL},
@@ -426,8 +426,8 @@ cpp_main(int argc, char ** argv)
               app.diffs = true;
               break;

-            case OPT_MERGES:
-              app.merges = true;
+            case OPT_NO_MERGES:
+              app.no_merges = true;
               break;

             case OPT_SET_DEFAULT:
============================================================
--- monotone.texi	1fcc35f2ac3ca9ba692509cd29f6f5ff01b6616a
+++ monotone.texi	d62e740d5bf36c317e4ec3fea155132bd7ba702d
@@ -34,7 +34,7 @@
 @page
 @vskip 0pt plus 1filll
 Copyright @copyright{} 2003, 2004 Graydon Hoare
-Copyright @copyright{} 2004, 2005 Nathaniel Smith
+Copyright @copyright{} 2004, 2005, 2006 Nathaniel Smith
 Copyright @copyright{} 2005 Derek Scherger
 Copyright @copyright{} 2005 Daniel Carosone
 All rights reserved
@@ -3186,7 +3186,7 @@ @section File Attributes
 quoted value for that attribute.  Stanzas are separated by blank lines.

 As a convenience, you can use the @code{monotone attr} command to set
-and view the values of these attributes; see @ref{Working Copy}.
+and view the values of these attributes; see @ref{Workspace}.

 You can tell monotone to automatically take actions based on these
 attributes by defining hooks; see the @code{attr_functions} entry in
@@ -3421,7 +3421,7 @@ @heading Incorporating New Changes
 examine descendents of your base revision, and ignore other heads on
 your branch.

-@heading Moving Working Copy to Another Revision
+@heading Moving Workspace to Another Revision

 @multitable @columnfractions .4 .4
 @item
@@ -3487,7 +3487,7 @@ @heading Viewing Differences
 @emph{revision IDs}, rather than file IDs.  If one leaves off the file
 argument, then diff can print the difference between two entire trees.

-@heading Showing Working Copy Status
+@heading Showing Workspace Status

 @multitable @columnfractions .4 .4
 @item
@@ -3508,7 +3508,7 @@ @heading Showing Working Copy Status
 difference is that monotone's @command{status} command always gives a
 status of the whole tree, and outputs a more compact summary than CVS.

-@heading Adding Directories and Files to Working Copy
+@heading Adding Directories and Files to Workspace

 @multitable @columnfractions .4 .4
 @item
@@ -3532,7 +3532,7 @@ @heading Adding Directories and Files to
 Directories are created as needed, and empty directories are ignored.


-@heading Removing Directories and Files from Working Copy
+@heading Removing Directories and Files from Workspace

 @multitable @columnfractions .4 .4
 @item
@@ -3631,7 +3631,7 @@ @chapter Command Reference

 @menu
 * Tree::             Operations on tree states in your database
-* Working Copy::     Operations on your workspace
+* Workspace::        Operations on your workspace
 * Network::          Communication with the network
 * Informative::      Production of descriptive reports
 * Key and Cert::     General operations on keys or certificates
@@ -3790,8 +3790,8 @@ @section Tree


 @page
-@node    Working Copy
-@section Working Copy
+@node    Workspace
+@section Workspace

 @ftable @command
 @item monotone setup [@var{directory}]
@@ -3826,7 +3826,7 @@ @section Working Copy
 any added entries inserted in its manifest.


-@item monotone drop @var{pathname...}
+@item monotone [--execute] drop @var{pathname...}
 @itemx monotone drop --missing
 This command places ``drop'' entries for the paths specified in
 @var{pathname...} in the workspace's ``work list''. The work list of
@@ -3850,6 +3850,10 @@ @section Working Copy
 you should run @command{drop}, and then perform the actual delete using
 whatever mechanism you normally use to delete files.

+The option @option{--execute} will make ``drop'' perform the actual
+deletion operations in the filesystem.  It will ignore files or
+directories which have already been deleted.
+
 @item monotone [--execute] rename @var{src} @var{dst}
 @itemx monotone [--execute] rename @var{src1} @var{...} @var{dst/}
 This command places ``rename'' entries for the paths specified in
@@ -4012,6 +4016,35 @@ @section Working Copy
 in inodeprints mode, and that the inodeprints cache is accurate and up
 to date.

+@item monotone pivot_root [--execute] pivot_root @var{new_root} @var{put_old}
+Most users will never need this command.  It is primarily useful in
+certain tricky cases where one wishes to combine several projects
+into one, or split one project into several.
+
+Its effect is to rename the directory whose name is currently
+@var{new_root} to become the root directory of the versioned tree, and
+to at the same time rename the directory that is currently the root of
+the versioned tree to have the name @var{put_old}.  Conceptually, it
+is equivalent to executing the following commands in the root of the
+workspace:
+
+@smallexample
+@group
+$ monotone rename . @var{new_root}/@var{put_old}
+$ monotone rename @var{new_root} .
+@end group
+@end smallexample
+
+Except, of course, that these @command{rename} commands are illegal,
+because after the first command the tree has no root at all, and there
+is a directory loop.  This illegality is the only reason for
+@command{pivot_root}'s existence; internally, the result is treated
+exactly like two renames, including with respect to merges and
+updates.
+
+The use of @option{--execute} with this command is strongly
+recommended, though not required.
+
 @end ftable

 @page
@@ -4139,7 +4172,7 @@ @section Informative
 to files changed within the current subdirectory of the workspace.

 @item monotone log
-@itemx monotone log [--last=@var{n}] [--next=@var{n}] [--revision=@var{id} [...]] [--brief] [--merges] [--no-files] [--diffs] [@var{file} [...]]
+@itemx monotone log [--last=@var{n}] [--next=@var{n}] [--revision=@var{id} [...]] [--brief] [--no-merges] [--no-files] [--diffs] [@var{file} [...]]

 This command prints out a log, in reverse-ancestry order, of small
 history summaries.  Each summary contains author, date, changelog and
@@ -4157,9 +4190,9 @@ @section Informative
 revision in forward-ancestry order.  This is useful to review changes
 that will be applied to the workspace when @command{update} is run.

-By default, the log entries for merge nodes are not shown; usually
-they don't contain much interesting information. If @code{--merges} is
-given, the log entries for these nodes will be included.
+By default, the log entries for merge nodes are shown.  If
+@code{--no-merges} is given, the log entries for these nodes will be
+excluded.

 If @code{--no-files} is given, the log output excludes the list of
 files changed in each revision.
============================================================
--- netcmd.cc	5a63ccd88ddb02cf5853e13ce3fff6ea20851a53
+++ netcmd.cc	da3773c93069c0834ce0b3caf34020305dd7d8b2
@@ -88,15 +88,6 @@ netcmd::read(string_queue & inbuf, chain
     return false;

   u8 extracted_ver = extract_datum_lsb<u8>(inbuf, pos, "netcmd protocol number");
-  if (extracted_ver != version)
-    throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'\n"
-                       "%s")
-                     % widen<u32,u8>(version)
-                     % widen<u32,u8>(extracted_ver)
-                     % ((version < extracted_ver)
-                        ? _("the remote side has a newer, incompatible version of monotone")
-                        : _("the remote side has an older, incompatible version of monotone")));
-  version = extracted_ver;

   u8 cmd_byte = extract_datum_lsb<u8>(inbuf, pos, "netcmd code");
   switch (cmd_byte)
@@ -115,8 +106,21 @@ netcmd::read(string_queue & inbuf, chain
       cmd_code = static_cast<netcmd_code>(cmd_byte);
       break;
     default:
-      throw bad_decode(F("unknown netcmd code 0x%x") % widen<u32,u8>(cmd_byte));
+      // if the versions don't match, we will throw the more descriptive
+      // error immediately after this switch.
+      if (extracted_ver == version)
+        throw bad_decode(F("unknown netcmd code 0x%x")
+                          % widen<u32,u8>(cmd_byte));
     }
+  // Ignore the version on usher_cmd packets.
+  if (extracted_ver != version && cmd_code != usher_cmd)
+    throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'\n"
+                       "%s")
+                     % widen<u32,u8>(version)
+                     % widen<u32,u8>(extracted_ver)
+                     % ((version < extracted_ver)
+                        ? _("the remote side has a newer, incompatible version of monotone")
+                        : _("the remote side has an older, incompatible version of monotone")));

   // check to see if we have even enough bytes for a complete uleb128
   size_t payload_len = 0;
============================================================
--- netcmd.hh	22664387cea0916b87288113ec5b559c2c49eda5
+++ netcmd.hh	e290b45f5acb01bfe181832d4750275e05b2f98f
@@ -69,9 +69,7 @@ public:
   std::string payload;
 public:
   netcmd();
-  netcmd(u8 _version);
   netcmd_code get_cmd_code() const {return cmd_code;}
-  u8 get_version() const {return version;}
   size_t encoded_size();
   bool operator==(netcmd const & other) const;

============================================================
--- options.hh	16194449c59ae0307ab9bbf15ae879f0f8063fdd
+++ options.hh	216cb82f6f82b63b0f1535b39bb985a237b43359
@@ -32,7 +32,7 @@
 #define OPT_MSGFILE 23
 #define OPT_BRIEF 24
 #define OPT_DIFFS 25
-#define OPT_MERGES 26
+#define OPT_NO_MERGES 26
 #define OPT_LAST 27
 #define OPT_NEXT 28
 #define OPT_VERBOSE 29
============================================================
--- paths.cc	c13e9b0c44a37a70e6c7e6d5c8b8869e363ccab9
+++ paths.cc	05c27908836d28a468070bb8e7a808417b8401f1
@@ -76,6 +76,7 @@ bookkeeping_path const bookkeeping_root(
 static access_tracker<system_path> working_root;

 bookkeeping_path const bookkeeping_root("MT");
+path_component const bookkeeping_root_component("MT");

 void
 save_initial_path()
@@ -438,13 +439,19 @@ system_path::system_path(any_path const

 system_path::system_path(any_path const & other, bool in_true_workspace)
 {
-  I(!is_absolute_here(other.as_internal()));
-  system_path wr;
-  if (in_true_workspace)
-    wr = working_root.get();
+  if (is_absolute_here(other.as_internal()))
+    // another system_path.  the normalizing isn't really necessary, but it
+    // makes me feel warm and fuzzy.
+    data = normalize_out_dots(other.as_internal());
   else
-    wr = working_root.get_but_unused();
-  data = normalize_out_dots((wr / other.as_internal()).as_internal());
+    {
+      system_path wr;
+      if (in_true_workspace)
+        wr = working_root.get();
+      else
+        wr = working_root.get_but_unused();
+      data = normalize_out_dots((wr / other.as_internal()).as_internal());
+    }
 }

 static inline std::string const_system_path(utf8 const & path)
@@ -468,6 +475,26 @@ system_path::system_path(utf8 const & pa
 }

 ///////////////////////////////////////////////////////////////////////////
+// utility
+///////////////////////////////////////////////////////////////////////////
+
+void
+dirname_basename(split_path const & sp,
+                 split_path & dirname, path_component & basename)
+{
+  I(!sp.empty());
+  // L(FL("dirname_basename('%s' [%d components],...)\n") % file_path(sp) % sp.size());
+  dirname = sp;
+  dirname.pop_back();
+  basename = sp.back();
+  if (dirname.empty())
+    {
+      // L(FL("basename %d vs. null component %d\n") % basename % the_null_component);
+      I(null_name(basename));
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////
 // workspace (and path root) handling
 ///////////////////////////////////////////////////////////////////////////

@@ -825,6 +852,7 @@ static void test_split_join()
   split_path split_mt1, split_mt2;
   file_path_internal("foo/MT").split(split_mt1);
   BOOST_CHECK(split_mt1.size() == 3);
+  I(split_mt1[2] == bookkeeping_root_component);
   split_mt2.push_back(the_null_component);
   split_mt2.push_back(split_mt1[2]);
   // split_mt2 now contains the component "MT"
============================================================
--- paths.hh	054a74b153b42a34140f2776f468bf79c844fa64
+++ paths.hh	e81d1f0e7efdbff539b8b92f04ad357770130fd4
@@ -207,6 +207,7 @@ extern bookkeeping_path const bookkeepin
 };

 extern bookkeeping_path const bookkeeping_root;
+extern path_component const bookkeeping_root_component;

 // this will always be an absolute path
 class system_path : public any_path
@@ -234,6 +235,9 @@ public:
   system_path operator /(std::string const & to_append) const;
 };

+void
+dirname_basename(split_path const & sp,
+                 split_path & dirname, path_component & basename);

 void
 save_initial_path();
============================================================
--- roster.cc	5313de9037e0af779b593f9f432155891a70374a
+++ roster.cc	ccbdfd50a2a99c49598ae638a013a095f232c876
@@ -305,23 +305,7 @@ roster_t::operator=(roster_t const & oth
   return *this;
 }

-void
-dirname_basename(split_path const & sp,
-                 split_path & dirname, path_component & basename)
-{
-  I(!sp.empty());
-  // L(FL("dirname_basename('%s' [%d components],...)\n") % file_path(sp) % sp.size());
-  split_path::const_iterator penultimate = sp.begin() + (sp.size()-1);
-  dirname = split_path(sp.begin(), penultimate);
-  basename = *penultimate;
-  if (dirname.empty())
-    {
-      // L(FL("basename %d vs. null component %d\n") % basename % the_null_component);
-      I(null_name(basename));
-    }
-}

-
 struct
 dfs_iter
 {
@@ -612,13 +596,10 @@ roster_t::detach_node(split_path const &
   path_component basename;
   dirname_basename(pth, dirname, basename);

+  I(has_root());
   if (dirname.empty())
     {
       // detaching the root dir
-      {
-        // detaching the root dir is currently forbidden.
-        I(false);
-      }
       I(null_name(basename));
       node_id root_id = root_dir->self;
       safe_insert(old_locations,
============================================================
--- roster.hh	0647777b7cab5a2c17419998351f049eadc706d5
+++ roster.hh	9ec5d3bbd5751f987a7591a3d577002a627a36e3
@@ -32,11 +32,6 @@ typedef boost::shared_ptr<dir_node> dir_
 typedef boost::shared_ptr<file_node> file_t;
 typedef boost::shared_ptr<dir_node> dir_t;

-// FIXME: perhaps move this to paths.{cc,hh}
-void
-dirname_basename(split_path const & sp,
-                 split_path & dirname, path_component & basename);
-
 node_id const the_null_node = 0;

 inline bool
============================================================
--- roster_merge.cc	aa38469bceafec9e94d2ba9c291cf08d6fc90e06
+++ roster_merge.cc	d1d307d2cc0fac8f4c50963b1563edcc3df26d86
@@ -25,7 +25,9 @@ roster_merge_result::is_clean_except_for
     && node_attr_conflicts.empty()
     && orphaned_node_conflicts.empty()
     && rename_target_conflicts.empty()
-    && directory_loop_conflicts.empty();
+    && directory_loop_conflicts.empty()
+    && illegal_name_conflicts.empty()
+    && !missing_root_dir;
 }

 void
@@ -81,6 +83,12 @@ roster_merge_result::log_conflicts()
       % directory_loop_conflicts[i].nid
       % directory_loop_conflicts[i].parent_name.first
       % directory_loop_conflicts[i].parent_name.second);
+
+  for (size_t i = 0; i < illegal_name_conflicts.size(); ++i)
+    L(FL("illegal name conflict: node %d, wanted parent %d, name %s")
+      % illegal_name_conflicts[i].nid
+      % illegal_name_conflicts[i].parent_name.first
+      % illegal_name_conflicts[i].parent_name.second);
 }

 void
@@ -121,6 +129,12 @@ roster_merge_result::warn_non_content_co
       % directory_loop_conflicts[i].nid
       % directory_loop_conflicts[i].parent_name.first
       % directory_loop_conflicts[i].parent_name.second);
+
+  for (size_t i = 0; i < illegal_name_conflicts.size(); ++i)
+    W(F("illegal name conflict: node %d, wanted parent %d, name %s")
+      % illegal_name_conflicts[i].nid
+      % illegal_name_conflicts[i].parent_name.first
+      % illegal_name_conflicts[i].parent_name.second);
 }

 void
@@ -132,6 +146,8 @@ roster_merge_result::clear()
   orphaned_node_conflicts.clear();
   rename_target_conflicts.clear();
   directory_loop_conflicts.clear();
+  illegal_name_conflicts.clear();
+  missing_root_dir = false;
   roster = roster_t();
 }

@@ -533,6 +549,29 @@ roster_merge(roster_t const & left_paren
     I(right_mi == right_markings.end());
     I(new_i == result.roster.all_nodes().end());
   }
+
+  // now check for the possible global problems
+  if (!result.roster.has_root())
+    result.missing_root_dir = true;
+  else
+    {
+      // we can't have an illegal MT dir unless we have a root node in the
+      // first place...
+      split_path bookkeeping_root_split;
+      bookkeeping_root_split.push_back(the_null_component);
+      bookkeeping_root_split.push_back(bookkeeping_root_component);
+      if (result.roster.has_node(bookkeeping_root_split))
+        {
+          illegal_name_conflict conflict;
+          node_t n = result.roster.get_node(bookkeeping_root_split);
+          conflict.nid = n->self;
+          conflict.parent_name.first = n->parent;
+          conflict.parent_name.second = n->name;
+          I(n->name == bookkeeping_root_component);
+          I(n->self == result.roster.detach_node(bookkeeping_root_split));
+          result.illegal_name_conflicts.push_back(conflict);
+        }
+    }
 }

 #ifdef BUILD_UNIT_TESTS
@@ -562,23 +601,28 @@ roster_merge(roster_t const & left_paren
 //   node attr, file and dir
 //
 // attr lifecycle:
-//   seen in both -- -->mark merge cases
-//   live in one and unseen in other
-//   dead in one and unseen in other
+//   seen in both -->mark merge cases, above
+//   live in one and unseen in other -->live
+//   dead in one and unseen in other -->dead
 //
 // two diff nodes with same name
 // directory loops
 // orphans
 // name collision on root dir
+// illegal node ("MT")
+// missing root dir
 //
 // interactions:
 //   in-node name conflict + possible between-node name conflict
 //   in-node name conflict + both possible names orphaned
 //   in-node name conflict + directory loop conflict
+//   in-node name conflict + one name illegal
 //   between-node name conflict + both nodes orphaned
 //   between-node name conflict + both nodes cause loop
+//   between-node name conflict + both nodes illegal

-// need roster, marking, birth revs, and uncommon ancestors for each side...
+// to run roster_merge, need roster, marking, birth revs, and uncommon
+// ancestors for each side...

 namespace
 {
============================================================
--- roster_merge.hh	9227745f9aeb67c68ca363dc1df62537c8bcb873
+++ roster_merge.hh	3a86138ac40adb1f2736fc9f348988b91aea2ff4
@@ -93,10 +93,19 @@ struct directory_loop_conflict
   std::pair<node_id, path_component> parent_name;
 };

-// renaming the root dir (as we currently do _not_) allows:
+// renaming the root dir allows these:
 //   -- MT in root
 //   -- missing root directory

+// this is a node that cleanly merged to some name, but that name was somehow
+// forbidden.  (Currently, the only forbidden name is "MT" in the root
+// directory.)
+struct illegal_name_conflict
+{
+  node_id nid;
+  std::pair<node_id, path_component> parent_name;
+};
+
 struct roster_merge_result
 {
   std::vector<node_name_conflict> node_name_conflicts;
@@ -105,6 +114,8 @@ struct roster_merge_result
   std::vector<orphaned_node_conflict> orphaned_node_conflicts;
   std::vector<rename_target_conflict> rename_target_conflicts;
   std::vector<directory_loop_conflict> directory_loop_conflicts;
+  std::vector<illegal_name_conflict> illegal_name_conflicts;
+  bool missing_root_dir;
   // this roster is sane if is_clean() returns true
   roster_t roster;
   bool is_clean();
============================================================
--- schema.sql	5e315b54e2c1df1f315c3aff2ad9a551332debb7
+++ schema.sql	c68ba3a5f158efdf6cae10d6b307a9cf0d35be44
@@ -22,15 +22,15 @@ CREATE TABLE files
 CREATE TABLE files
 	(
 	id primary key,   -- strong hash of file contents
-	data not null,    -- compressed, encoded contents of a file
+	data not null     -- compressed contents of a file
 	size integer not null     -- length(data)
 	);

 CREATE TABLE file_deltas
-	(
-	id not null,          -- strong hash of file contents
-	base not null,        -- joins with files.id or file_deltas.id
-	delta not null,       -- rdiff to construct current from base
+	(
+	id not null,      -- strong hash of file contents
+	base not null,    -- joins with files.id or file_deltas.id
+	delta not null,   -- compressed rdiff to construct current from base
 	path_dist integer not null,   -- 1 if base is full, otherwise path_dist(base)+1
 	path_size integer not null,   -- size + size of base
 	size integer not null,        -- length(delta)
============================================================
--- schema_migration.cc	9f3a9b8a4a509edf8af01c796cbc6588e902e888
+++ schema_migration.cc	7d215b15eb77f65457092c0cc1553f4ab2689a4e
@@ -20,6 +20,7 @@
 #include "botan/botan.h"
 #include "app_state.hh"
 #include "keys.hh"
+#include "transforms.hh"

 // this file knows how to migrate schema databases. the general strategy is
 // to hash each schema we ever use, and make a list of the SQL commands
@@ -54,12 +55,8 @@ typedef boost::tokenizer<boost::char_sep

 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;

-extern "C" {
-  const char *sqlite3_value_text_s(sqlite3_value *v);
-}
-
 static string
-lowercase(string const & in)
+lowercase_facet(string const & in)
 {
   I(40==in.size());
   const int sz=40;
@@ -93,7 +90,7 @@ calculate_id(string const & in,
   Botan::Pipe p(new Botan::Hash_Filter("SHA-1"), new Botan::Hex_Encoder());
   p.process_msg(in);

-  ident = lowercase(p.read_all_as_string());
+  ident = lowercase_facet(p.read_all_as_string());
 }


@@ -118,19 +115,19 @@ sqlite_sha1_fn(sqlite3_context *f, int n

   if (nargs == 1)
     {
-      string s = (sqlite3_value_text_s(args[0]));
+      string s = reinterpret_cast<char const*>(sqlite3_value_text(args[0]));
       s.erase(remove_if(s.begin(), s.end(), is_ws()),s.end());
       tmp = s;
     }
   else
     {
-      string sep = string(sqlite3_value_text_s(args[0]));
-      string s = (sqlite3_value_text_s(args[1]));
+      string sep = string(reinterpret_cast<char const*>(sqlite3_value_text(args[0])));
+      string s = reinterpret_cast<char const*>(sqlite3_value_text(args[1]));
       s.erase(remove_if(s.begin(), s.end(), is_ws()),s.end());
       tmp = s;
       for (int i = 2; i < nargs; ++i)
         {
-          s = string(sqlite3_value_text_s(args[i]));
+          s = string(reinterpret_cast<char const*>(sqlite3_value_text(args[i])));
           s.erase(remove_if(s.begin(), s.end(), is_ws()),s.end());
           tmp += sep + s;
         }
@@ -908,6 +905,135 @@ migrate_client_to_add_rosters(sqlite3 *
   return true;
 }

+static void
+sqlite3_unbase64_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
+{
+  if (nargs != 1)
+    {
+      sqlite3_result_error(f, "need exactly 1 arg to unbase64()", -1);
+      return;
+    }
+  data decoded;
+  decode_base64(base64<data>(string(reinterpret_cast<char const*>(sqlite3_value_text(args[0])))), decoded);
+  sqlite3_result_blob(f, decoded().c_str(), decoded().size(), SQLITE_TRANSIENT);
+}
+
+// I wish I had a form of ALTER TABLE COMMENT on sqlite3
+static bool
+migrate_files_BLOB(sqlite3 * sql,
+                                    char ** errmsg,
+                                    app_state *app)
+{
+  int res;
+  I(sqlite3_create_function(sql, "unbase64", -1,
+                           SQLITE_UTF8, NULL,
+                           &sqlite3_unbase64_fn,
+                           NULL, NULL) == 0);
+  // change the encoding of file(_delta)s
+  if (!move_table(sql, errmsg,
+                  "files",
+                  "tmp",
+                  "("
+                  "id primary key,"
+                  "data not null"
+                  ")"))
+    return false;
+
+  res = logged_sqlite3_exec(sql, "CREATE TABLE files\n"
+                            "\t(\n"
+                            "\tid primary key,   -- strong hash of file contents\n"
+                            "\tdata not null     -- compressed contents of a file\n"
+                            "\t)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "INSERT INTO files "
+                            "SELECT id, unbase64(data) "
+                            "FROM tmp", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  if (!move_table(sql, errmsg,
+                  "file_deltas",
+                  "tmp",
+                  "("
+                  "id not null,"
+                  "base not null,"
+                  "delta not null"
+                  ")"))
+    return false;
+
+  res = logged_sqlite3_exec(sql, "CREATE TABLE file_deltas\n"
+                            "\t(\n"
+                            "\tid not null,      -- strong hash of file contents\n"
+                            "\tbase not null,    -- joins with files.id or file_deltas.id\n"
+                            "\tdelta not null,   -- compressed rdiff to construct current from base\n"
+                            "\tunique(id, base)\n"
+                            "\t)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "INSERT INTO file_deltas "
+                            "SELECT id, base, unbase64(delta) "
+                            "FROM tmp", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  // migrate other contents which are accessed by get|put_version
+  res = logged_sqlite3_exec(sql, "UPDATE manifests SET data=unbase64(data)",
+                            NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE manifest_deltas "
+                            "SET delta=unbase64(delta)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE rosters SET data=unbase64(data) ",
+                            NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE roster_deltas "
+                            "SET delta=unbase64(delta)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "UPDATE db_vars "
+      "SET value=unbase64(value),name=unbase64(name)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE public_keys "
+      "SET keydata=unbase64(keydata)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE revision_certs "
+      "SET value=unbase64(value),signature=unbase64(signature)",
+      NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE manifest_certs "
+      "SET value=unbase64(value),signature=unbase64(signature) ",
+      NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE revisions "
+      "SET data=unbase64(data)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE branch_epochs "
+      "SET branch=unbase64(branch)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  return true;
+}
+
 void
 migrate_monotone_schema(sqlite3 *sql, app_state *app)
 {
@@ -939,6 +1065,9 @@ migrate_monotone_schema(sqlite3 *sql, ap
   m.add("bd86f9a90b5d552f0be1fa9aee847ea0f317778b",
         &migrate_client_to_add_rosters);

+  m.add("1db80c7cee8fa966913db1a463ed50bf1b0e5b0e",
+        &migrate_files_BLOB);
+
   // IMPORTANT: whenever you modify this to add a new schema version, you must
   // also add a new migration test for the new schema version.  See
   // tests/t_migrate_schema.at for details.
============================================================
--- std_hooks.lua	87a925a89f17ae312390049e28296bbb9f7ee170
+++ std_hooks.lua	4ca1188ac09063710baa429986649f2c3ad13083
@@ -70,6 +70,15 @@ attr_functions["mtn:execute"] =
       end
    end

+function dir_matches(name, dir)
+   -- helper for ignore_file, matching files within dir, or dir itself.
+   -- eg for dir of 'CVS', matches CVS/, CVS/*, */CVS/ and */CVS/*
+   if (string.find(name, "^" .. dir .. "/")) then return true end
+   if (string.find(name, "^" .. dir .. "$")) then return true end
+   if (string.find(name, "/" .. dir .. "/")) then return true end
+   if (string.find(name, "/" .. dir .. "$")) then return true end
+   return false
+end

 function ignore_file(name)
    -- project specific
@@ -124,26 +133,21 @@ function ignore_file(name)
    -- emacs creates #foo# files
    if (string.find(name, "%#[^/]*%#$")) then return true end
    -- autotools detritus:
-   if (string.find(name, "^autom4te.cache/")) then return true end
-   if (string.find(name, "/autom4te.cache/")) then return true end
-   if (string.find(name, "^.deps/")) then return true end
-   if (string.find(name, "/.deps/")) then return true end
+   if dir_matches(name, "autom4te.cache") then return true end
+   if dir_matches(name, ".deps") then return true end
    -- Cons/SCons detritus:
-   if (string.find(name, "^.consign$")) then return true end
-   if (string.find(name, "/.consign$")) then return true end
-   if (string.find(name, "^.sconsign$")) then return true end
-   if (string.find(name, "/.sconsign$")) then return true end
+   if dir_matches(name, ".consign") then return true end
+   if dir_matches(name, ".sconsign") then return true end
    -- other VCSes:
-   if (string.find(name, "^CVS/")) then return true end
-   if (string.find(name, "/CVS/")) then return true end
-   if (string.find(name, "^%.svn/")) then return true end
-   if (string.find(name, "/%.svn/")) then return true end
-   if (string.find(name, "^SCCS/")) then return true end
-   if (string.find(name, "/SCCS/")) then return true end
-   if (string.find(name, "^_darcs/")) then return true end
-   if (string.find(name, "^.cdv/")) then return true end
-   if (string.find(name, "^.git/")) then return true end
-   if (string.find(name, "%.scc$")) then return true end
+   if dir_matches(name, "CVS") then return true end
+   if dir_matches(name, ".svn") then return true end
+   if dir_matches(name, "SCCS") then return true end
+   if dir_matches(name, "_darcs") then return true end
+   if dir_matches(name, ".cdv") then return true end
+   if dir_matches(name, ".git") then return true end
+   if dir_matches(name, ".scc") then return true end
+   if dir_matches(name, ".bzr") then return true end
+   if dir_matches(name, ".hg") then return true end
    -- desktop/directory configuration metadata
    if (string.find(name, "^.DS_Store$")) then return true end
    if (string.find(name, "/.DS_Store$")) then return true end
============================================================
--- tests/t_log_nofiles_merges.at	958b5e4229b1afd2c7ec131d0a239b28d792815f
+++ tests/t_log_nofiles_nomerges.at	dedc7b333ac5e779f29464a1f62285ec787b6dd4
@@ -30,12 +30,12 @@ R2=`BASE_REVISION`
 AT_CHECK(MONOTONE update, [], [ignore], [ignore])
 R2=`BASE_REVISION`

-# check that merge is excluded from log output
+# check that merge is included by default
 AT_CHECK(MONOTONE log, [], [stdout], [ignore])
-AT_CHECK(grep '^Revision' stdout | grep $R2, [1], [ignore])
+AT_CHECK(grep '^Revision' stdout | grep $R2, [], [ignore])

-# and that it has been included by --merges
-AT_CHECK(MONOTONE log --merges, [], [stdout], [ignore])
-AT_CHECK(grep '^Revision' stdout | grep $R2, [], [ignore])
+# and that it is excluded by --no-merges
+AT_CHECK(MONOTONE log --no-merges, [], [stdout], [ignore])
+AT_CHECK(grep '^Revision' stdout | grep $R2, [1], [ignore])

 AT_CLEANUP
============================================================
--- tests/t_migrate_schema.at	9d9a73cc20f8e367f4c828484e78c19224614b93
+++ tests/t_migrate_schema.at	e5b24dd9ccc44e36b20d1b1dc89509f0e99896fd
@@ -284,4 +284,181 @@ 5Zv/XbuXQ1m/fTpA9kr7T0e2Pv780pGhadLlP3b/
 5Zv/XbuXQ1m/fTpA9kr7T0e2Pv780pGhadLlP3b/B/9helPJSAAA
 ])

+CHECK_MIGRATE_FROM([9d2b5d7b86df00c30ac34fe87a3c20f1195bb2df],
+[H4sIANfQ6UMCA+2cW2+dR5aer61fQfSNbEDt1Pkwc9OeiZEY6HEDbc+ggSAQ6tjiRCYVkupp
+//s870dLoiTS3pQyyE2sbomHvb86rfUeqlbtf/r2v333/dm3f/nnP/7rD9/927f/+OSf//zt
+Nz9+e/bjN//0x2/P+lW7GC+er1eX48X1ky++fPLFi3b94uzi8ubs4vXLl2evL87/9+v17OzN
+f7///dnxgst9drV+aucX5xd/Pdvn6+W8Prter9pVu1nzrP989rt/+N2TL26f/tDTeNa/X55f
+XJ/9x/nNCx73t/Pr88uL52Nd3Vx//bf28vV68sXRsXcP+OA/nkAL8/Knsxfr779fF+Ny0vr5
+fPLFVx+Mc/bnf2tX10/evPPLt1/xbobxtolnb598PS5fLQ20vXr18ny0Gzp3fHvGk96+/aL9
+tD588/F2XnT2v9bPb194DOijV/7ywtvRvnnp7TR9eduzZ0cTX7395Ycj2+cv1/O5Xt602/U7
+n/eN5ebqkoV6s3R6z9m4vLhZFze8q7frD3r2/tLo5ddf8+DLq7vNfa2JPr587828d1z+9Opq
+XV+zGFfzfO+zm0s1Ry9ej5uz8frqiobP9hULp7affPHLiM/ns+MHXx0L+N33P3z75x/Pvvv+
+xz/dbfbs3775479++8OXT1sdLRubam6zLZNSr2ma3Frv3mwzkwsxVTeePns6Q3V1u5Cm8y3a
+1VvKtaYSlyu7tm5y7qH49vTZX57aXbop5s5/e28fgzcrhua9G96vsJxxxqw2SnOxmnb7yqcn
+dNunXV0JZo69m3Mmhe7mrMuUUKYNPtL3PKK6veuMtrSwrDdl9FWi8X3OsmMu3aa5QjA5VHtS
+t9u7bvvW+deux3TbVPo3QvK2OdOGTcmHsCPD93stE1oaKaQW6LZdYyXD9Nbda0huLhezTdaa
+nGpv07ZiVpnlpG7Xd92uLeTgqr3b7Y9y4W0WvLo6/6ld/awkfHZKFsx2J5DfJM6dSH7zylsM
+0JvvjdJ3M/a5QRf6L8F2DJ6Z9ExsNPHhNXvXdu+rzV0WM+nKqCt6npPtaNvFEm2oxZu0evrV
+tpemXm1vO+knwXZS258btUfb7c24Y16j9ZlPa/tzQ+9ou75pu4Akw2x3t+33Au6ndnG+1/XN
+LWn9Kn8+jjk/RvH7OPNN82/A+W137gD0/QR1POnyVaNzSgv1Zby4vF4X6sHr63X15Iv7Cevu
+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
+D6TSUF9iWAAA
+])
+
 AT_CLEANUP
============================================================
--- tests/t_sql_unpack.at	1bb994c7c10fcfafc2a0fc9b5dd7c45b5b315174
+++ tests/t_sql_gunzip.at	1e43988cec90fe9e16a51379b7b832cfc609d122
@@ -1,13 +1,9 @@
-AT_SETUP([sql functions unpack, unbase64])
+AT_SETUP([sql function gunzip (which replaced unpack)])
 MONOTONE_SETUP

-AT_CHECK(MONOTONE db execute "select unpack('H4sIAAAAAAAAAE2RQW4bMQwA7/uKRe5JJEoUyXOu/YFhGJREJgvEq8BetOjvq/bg9EyQMxju9uty1X1zux/riQSbM2lsiZxSzI6qhSVayh0KKSF51XJelvHZLzf7ud23sa8n65KtJSfOkUTcQyRVLMFRIPfgSt4C9vO/vW8gGJVMjiFxCIEqd0rgbowhZ6AOoQAUlQn80qN9rE8v1+NZj+N2f1pWv43rlK6F0MQ6ilnrUnWKZHMGjlLaPCEdVdJ5Wdf1GOvJpRbs4hGpWiVJUCFYVkabg8wkWbTbf9C3D93f7cd4f0Ch6kySCnTiktRQggALCSXClr2TYYnAD2ggDIXD1IyhUmIs3jqyxb9dHG3G6jXH/A1tYz9uW31tm9ZxXK5jH8fY7eXr98NiLnNjT1VxhgR2lQoxSw05okaKyQtJxIcFJ20cerbSySMDzq+CZbKoSVKtmDqgVjgvfwA6AWjaGQIAAA==')", [], [stdout], [ignore])
+AT_CHECK(MONOTONE db execute "select gunzip(X'1f8b08000000000000004d91416e1b310c00effb8a45ee49244a14c973aefd8161189444260bc4abc05eb4e8efabf6e0f44c903318eef6eb72d57d73bb1feb89049b33696c899c52cc8eaa85255aca1d0a292179d5725e96f1d92f37fbb9ddb7b1af27eb92ad2527ce9144dc4324552cc15120f7e04ade02f6f3bfbd6f2018954c8e217108812a774ae06e8c2167a00ea100149509fcd2a37dac4f2fd7e3598fe3767f5a56bf8deb94ae85d0c43a8a59eb52758a6473068e52da3c211d55d27959d7f518ebc9a516ece211a95a254950215856469b83cc2459b4db7fd0b70fdddfedc7787f40a1ea4c920a74e292d45082000b0925c296bd936189c00f68200c85c3d48ca152622cde3ab2c5bf5d1c6dc6ea35c7fc0d6d633f6e5b7d6d9bd6715cae631fc7d8ede5ebf7c3622e73634f5571860476950a314b0d39a2468ac90b49c48705276d1c7ab6d2c92303ceaf8265b2a84952ad983aa056382f7f003a0168da19020000')", [], [stdout], [ignore])

 AT_CHECK(QGREP('new_manifest \@<:@795cf87a1c37f7314f5aa6891e34d267a757fba6\@:>@' stdout))
 AT_CHECK(QGREP('from \@<:@7b675e9ed59eecd9bac3f4ef828196c2069d5a93\@:>@' stdout))

-AT_CHECK(MONOTONE db execute "select unbase64('aXQncyB0ZW4gcGFjZXMgbm9ydGh3ZXN0IG9mIHRoZSBwYWxtIHRyZWUuICBGT09CQVI=')", [], [stdout], [ignore])
-
-AT_CHECK(QGREP(FOOBAR stdout))
-
 AT_CLEANUP
============================================================
--- testsuite.at	c8be08d87b48f90c833c326e708aaa06423b1b75
+++ testsuite.at	d67ef3223629641b02551a0a31cbf4644cd60d82
@@ -639,7 +639,7 @@ m4_include(tests/t_db_execute.at)
 m4_include(tests/t_commit_log_3.at)
 m4_include(tests/t_at_sign.at)
 m4_include(tests/t_db_execute.at)
-m4_include(tests/t_sql_unpack.at)
+m4_include(tests/t_sql_gunzip.at)
 m4_include(tests/t_final_space.at)
 m4_include(tests/t_inodeprints.at)
 m4_include(tests/t_inodeprints_update.at)
@@ -776,6 +776,11 @@ m4_include(tests/t_db_kill_rev_locally_2
 m4_include(tests/t_ls_changed.at)
 m4_include(tests/t_revert_new_project.at)
 m4_include(tests/t_db_kill_rev_locally_2.at)
-m4_include(tests/t_log_nofiles_merges.at)
+m4_include(tests/t_log_nofiles_nomerges.at)
 m4_include(tests/t_cvsimport_branch.at)
 m4_include(tests/t_log_to_file.at)
+m4_include(tests/t_log_selectors.at)
+m4_include(tests/t_pivot_root.at)
+m4_include(tests/t_pivot_root_revert.at)
+m4_include(tests/t_pivot_root_update.at)
+m4_include(tests/t_rosterify_root_suture.at)
============================================================
--- work.cc	39e5c9ecd7f5209dbcbb7e631d6fc044296a4a67
+++ work.cc	be9eb83e519a1395626b2b29868af63e46a958c2
@@ -28,7 +28,7 @@ string const attr_file_name(".mt-attrs")

 string const attr_file_name(".mt-attrs");

-void
+void
 file_itemizer::visit_dir(file_path const & path)
 {
   this->visit_file(path);
@@ -347,7 +347,70 @@ perform_rename(set<file_path> const & sr
   update_any_attrs(app);
 }

+void
+perform_pivot_root(file_path const & new_root, file_path const & put_old,
+                   app_state & app)
+{
+  split_path new_root_sp, put_old_sp, root_sp;
+  new_root.split(new_root_sp);
+  put_old.split(put_old_sp);
+  file_path().split(root_sp);

+  temp_node_id_source nis;
+  roster_t base_roster, new_roster;
+  get_base_and_current_roster_shape(base_roster, new_roster, nis, app);
+
+  I(new_roster.has_root());
+  N(new_roster.has_node(new_root_sp),
+    F("proposed new root directory '%s' is not versioned or does not exist") % new_root);
+  N(is_dir_t(new_roster.get_node(new_root_sp)),
+    F("proposed new root directory '%s' is not a directory") % new_root);
+  {
+    split_path new_root_MT;
+    (new_root / bookkeeping_root.as_internal()).split(new_root_MT);
+    N(!new_roster.has_node(new_root_MT),
+      F("proposed new root directory '%s' contains illegal path %s") % new_root % bookkeeping_root);
+  }
+
+  {
+    file_path current_path_to_put_old = (new_root / put_old.as_internal());
+    split_path current_path_to_put_old_sp, current_path_to_put_old_parent_sp;
+    path_component basename;
+    current_path_to_put_old.split(current_path_to_put_old_sp);
+    dirname_basename(current_path_to_put_old_sp, current_path_to_put_old_parent_sp, basename);
+    N(new_roster.has_node(current_path_to_put_old_parent_sp),
+      F("directory '%s' is not versioned or does not exist")
+      % file_path(current_path_to_put_old_parent_sp));
+    N(is_dir_t(new_roster.get_node(current_path_to_put_old_parent_sp)),
+      F("'%s' is not a directory")
+      % file_path(current_path_to_put_old_parent_sp));
+    N(!new_roster.has_node(current_path_to_put_old_sp),
+      F("'%s' is in the way") % current_path_to_put_old);
+  }
+
+  cset cs;
+  safe_insert(cs.nodes_renamed, std::make_pair(root_sp, put_old_sp));
+  safe_insert(cs.nodes_renamed, std::make_pair(new_root_sp, root_sp));
+
+  {
+    editable_roster_base e(new_roster, nis);
+    cs.apply_to(e);
+  }
+  {
+    cset new_work;
+    make_cset(base_roster, new_roster, new_work);
+    put_work_cset(new_work);
+  }
+  if (app.execute)
+    {
+      empty_file_content_source efcs;
+      editable_working_tree e(app, efcs);
+      cs.apply_to(e);
+    }
+  update_any_attrs(app);
+}
+
+
 // work file containing rearrangement from uncommitted adds/drops/renames

 std::string const work_file_name("work");
@@ -599,7 +662,7 @@ read_options_map(data const & dat, optio
       parser.sym(opt);
       parser.str(val);
       // options[opt] = val;
-      // use non-replacing insert verses replacing with options[opt] = val;
+      // use non-replacing insert versus replacing with options[opt] = val;
       options.insert(make_pair(opt, val));
     }
 }
@@ -732,34 +795,58 @@ editable_working_tree::editable_working_

 editable_working_tree::editable_working_tree(app_state & app,
                                              file_content_source const & source)
-  : app(app), source(source), next_nid(1)
+  : app(app), source(source), next_nid(1), root_dir_attached(true)
 {
 }

-void
-move_path_if_not_already_present(any_path const & old_path,
-                                 any_path const & new_path,
-                                 app_state & app)
-{
-}
-
 static inline bookkeeping_path
 path_for_nid(node_id nid)
 {
   return bookkeeping_root / "tmp" / boost::lexical_cast<std::string>(nid);
 }

+// Attaching/detaching the root directory:
+//   This is tricky, because we don't want to simply move it around, like
+// other directories.  That would require some very snazzy handling of the
+// MT directory, and never be possible on windows anyway[1].  So, what we do
+// is fake it -- whenever we want to move the root directory into the
+// temporary dir, we instead create a new dir in the temporary dir, move
+// all of the root's contents into this new dir, and make a note that the root
+// directory is logically non-existent.  Whenever we want to move some
+// directory out of the temporary dir and onto the root directory, we instead
+// check that the root is logically nonexistent, move its contents, and note
+// that it exists again.
+//
+// [1] Because the root directory is our working directory, and thus locked in
+// place.  We _could_ chdir out, then move MT out, then move the real root
+// directory into our newly-moved MT, etc., but aside from being very finicky,
+// this would require that we know our root directory's name relative to its
+// parent.
+
 node_id
 editable_working_tree::detach_node(split_path const & src)
 {
+  I(root_dir_attached);
   node_id nid = next_nid++;
   file_path src_pth(src);
-  // can't detach the root dir
-  E(!(src_pth == file_path()), F("cannot delete or rename the root directory"));
   bookkeeping_path dst_pth = path_for_nid(nid);
   safe_insert(rename_add_drop_map, make_pair(dst_pth, src_pth));
   make_dir_for(dst_pth);
-  move_path(src_pth, dst_pth);
+  if (src_pth == file_path())
+    {
+      // root dir detach, so we move contents, rather than the dir itself
+      mkdir_p(dst_pth);
+      std::vector<utf8> files, dirs;
+      read_directory(src_pth, files, dirs);
+      for (std::vector<utf8>::const_iterator i = files.begin(); i != files.end(); ++i)
+        move_file(src_pth / (*i)(), dst_pth / (*i)());
+      for (std::vector<utf8>::const_iterator i = dirs.begin(); i != dirs.end(); ++i)
+        if (!bookkeeping_path::is_bookkeeping_path((*i)()))
+          move_dir(src_pth / (*i)(), dst_pth / (*i)());
+      root_dir_attached = false;
+    }
+  else
+    move_path(src_pth, dst_pth);
   return nid;
 }

@@ -807,20 +894,14 @@ editable_working_tree::attach_node(node_

   // Possibly just write data out into the workspace, if we're doing
   // a file-create (not a dir-create or file/dir rename).
-  if (!file_exists(src_pth))
+  if (!path_exists(src_pth))
     {
+      I(root_dir_attached);
       std::map<bookkeeping_path, file_id>::const_iterator i
         = written_content.find(src_pth);
       if (i != written_content.end())
         {
-          if (file_exists(dst_pth))
-            {
-              file_id dst_id;
-              ident_existing_file(dst_pth, dst_id, app.lua);
-              if (i->second == dst_id)
-                return;
-            }
-          P(F("adding %s") % dst_pth);
+          P(F("adding '%s'") % dst_pth);
           file_data dat;
           source.get_file_content(i->second, dat);
           write_localized_data(dst_pth, dat.inner(), app.lua);
@@ -828,33 +909,47 @@ editable_working_tree::attach_node(node_
         }
     }

+  // FIXME: it is weird to do this here, instead of up above, but if we do it
+  // up above a lot of tests break.  those tests are arguably broken -- they
+  // depend on 'update' clobbering existing, non-versioned files -- but
+  // putting this up there doesn't actually help, since if we abort in the
+  // middle of an update to avoid clobbering a file, we just end up leaving
+  // the working copy in an inconsistent state instead.  so for now, we leave
+  // this check down here.
+  require_path_