monotone-commits-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Monotone-commits-diffs] net.venge.monotone.colored-diff: d4165db330a17


From: code
Subject: [Monotone-commits-diffs] net.venge.monotone.colored-diff: d4165db330a1781b7016a401c21a1c25fa952894
Date: Mon, 21 Mar 2011 14:40:29 +0100 (CET)

revision:            d4165db330a1781b7016a401c21a1c25fa952894
date:                2011-02-23T10:25:14
author:              Richard Hopkins <address@hidden>
branch:              net.venge.monotone.colored-diff
changelog:
propagate from branch 'net.venge.monotone' (head 
9fa8e839780984c1028b5023f47d525066d99535)
            to branch 'net.venge.monotone.colored-diff' (head 
7b930320caeeaadddae9c9556a50fdd8ee18540a)

manifest:
format_version "1"

new_manifest [2712adbed9ffa51ba58c4188a7a333bb7e83c589]

old_revision [7b930320caeeaadddae9c9556a50fdd8ee18540a]

rename "test/func/netsync_key_hook"
    to "test/func/netsync_key_hooks"

add_dir "test/func/netsync_bind_opt"

add_file "extra/bin/mtn-cleanup.1"
 content [b898678b0775bf10a7428cfd8b52a4883edddbcc]

add_file "src/util/mtnopt.1"
 content [8e24a1cafa100a7d6681cc19e30ee82a4cf517f4]

add_file "test/func/netsync_bind_opt/__driver__.lua"
 content [153c2be3d3c3c37b4fea590633d2d3be9bae5e82]

add_file "test/func/netsync_key_hooks/server-hooks.lua"
 content [434dab628a58b2c644f5ad6997420f1186cabbb5]

patch ".mtn-ignore"
 from [ee776b54b58b1d3cc4265cdfa09a6d426ae1f6c5]
   to [347361ce3fa47016cbe4f5ba29188696d939a1d8]

patch "Makefile.am"
 from [3987cf2943284d2dbe2722925395ac1544b6d1fc]
   to [ab8b3972917db2d7e2a0bfd42abaea31a66c143f]

patch "NEWS"
 from [c99a1b030116f68845e9418fd53277c04eb53297]
   to [fa80c1f7aeaaf7278731bb1f7c7cd0a94f7b3c4b]

patch "contrib/monotone-cluster-push.lua"
 from [2ee0e849058ea7bbef9487fc21772d1fb66500d6]
   to [3a4f3bd0394d70d5c07c9314ad42026b212a4983]

patch "doc/monotone.texi"
 from [8d7a3efa8e20f906edc0883785c6c8f51cc6919c]
   to [14de7e0ea6d88053129594b1628cac25ebf14506]

patch "extra/shell/monotone.bash_completion.in"
 from [f7d35f2c13c6babc009800a11f6eff1eb69b01d9]
   to [a878e6fa815f84f18961fb2366d0235376be8c29]

patch "src/cmd_list.cc"
 from [12643b2161237863a8de6656c99328dc6ea3c7e6]
   to [eede6b49bfe267b8e9b361c12bbcc26f53e86191]

patch "src/database.cc"
 from [8966518247a144a25de01b4ab8fa9e515661edfb]
   to [f61bd0d7d66a715c7a55cb943eb8521db6d91d68]

patch "src/keys.cc"
 from [bdf4a6b94902d29411b16cb169851986a16b3956]
   to [19db9fa939a1d87aba964f4e84743adcfff6fdd2]

patch "src/lua_hooks.cc"
 from [cd982157b1248b75f37edda84723e8944c70c5bf]
   to [81dd3985d253ac62743df0341e60a74d5b93b755]

patch "src/lua_hooks.hh"
 from [a7ab69e459aa1f8450ac10704f0c22a595cc69e8]
   to [51a2842d22554cba640dcea416103f5e70c63c3b]

patch "src/network/connection_info.cc"
 from [78789566ad076c92d8795ea7e3877646c3749693]
   to [b732c16161db2394aac449f50c6a6921e553ef22]

patch "src/network/connection_info.hh"
 from [8841c299c1a214c46071b6f77c3a5dbadeaf4959]
   to [0ba5fc1778b78fb3446ad345ec78e073fd9221e4]

patch "test/extra/mtn-cleanup/__driver__.lua"
 from [c0a63dd6bbb69d30db6f6b64f7d59f2b5f1d00c7]
   to [f650305a0d130a4ac6fb988eed58662a564dcc64]

patch "test/func/manpage/__driver__.lua"
 from [2230d26ba35875be86cbb8000b4c0c8c7d36f735]
   to [a381ef4517c907170547a4984923650617f69c60]

patch "test/func/netsync_key_hooks/__driver__.lua"
 from [87963f895052ee9dd35d513e23004811169bca38]
   to [d7fad5a70688d3db512a05ebb0e3643006f8c7e2]

patch "test/func/netsync_key_hooks/client-hooks.lua"
 from [f60d553a6bff838e53904f94294588d9f9ed84ee]
   to [e9eb38666d23a1f4a5be33404220902ee90756eb]

old_revision [9fa8e839780984c1028b5023f47d525066d99535]

add_file "src/colorizer.cc"
 content [96964884a83e4d7d638c146821a554d033f396eb]

add_file "src/colorizer.hh"
 content [eb3d0e3dad8d447e270e0c6264bf1a4ea139013f]

patch "Makefile.am"
 from [6e6a3f7aaecccc749e261d452d84bd8431ba6cb7]
   to [ab8b3972917db2d7e2a0bfd42abaea31a66c143f]

patch "NEWS"
 from [848a8d23c760b709d5954ef1ca4a812c72404343]
   to [fa80c1f7aeaaf7278731bb1f7c7cd0a94f7b3c4b]

patch "doc/monotone.texi"
 from [5c18cff828089e592f5f2d53c2d60793715a4a40]
   to [14de7e0ea6d88053129594b1628cac25ebf14506]

patch "src/asciik.cc"
 from [cf946f9a14ad309615704bc960255c50e12b636a]
   to [64600c9969226fa55cd05982364147342d3b734e]

patch "src/asciik.hh"
 from [592aa966af256f50be9784bfd01c543f54d3447b]
   to [3e0dcd90804053a5e558b0ddfa6e5f3dcc462d50]

patch "src/cmd_diff_log.cc"
 from [94a354875b587d372e6d0898ab6fc8355ce29c21]
   to [c8d5d47eecd08a5935b6739b640313effdfae100]

patch "src/cmd_files.cc"
 from [7e9ffd4a06daeb0ef7ab7557d3a656c6b99ab2ad]
   to [44c13d2a7ae5e2beb2b316d24202481680779f70]

patch "src/cmd_ws_commit.cc"
 from [7b91a53d8eec4e78b062092f85c945bbaaefe23a]
   to [e0bd38df40b3be50ffd8793c134fcc9edfb47bd9]

patch "src/diff_output.cc"
 from [8df7c0bec2e64275f6c8e8b6ccb035c8fd0f684f]
   to [db19431f96f35ca8c6b89c129c2a805ddd418f15]

patch "src/diff_output.hh"
 from [9125ccd0d0fa725782c9910b5f34e844048d2da8]
   to [168281addd27cdade1dc1320ee4814b28070feb8]

patch "src/lua_hooks.cc"
 from [1c178085332c73dcefb4681d205d17b059e52080]
   to [81dd3985d253ac62743df0341e60a74d5b93b755]

patch "src/lua_hooks.hh"
 from [66412b9fa5db97cd3b3ec01cadf036fb346ac161]
   to [51a2842d22554cba640dcea416103f5e70c63c3b]

patch "src/options_list.hh"
 from [4af93dbcc45e76d732d073ced7ce21d20472cd00]
   to [e7b13e91324a35e0b05be40e49f66cc654fa2f7e]

patch "src/rev_output.cc"
 from [a2c70b893b31296917d1a2b974faa1da46c13f1e]
   to [fec6728d80bca2d8d13ab49bb4ecd03310879fe5]

patch "src/rev_output.hh"
 from [666dd3ed35e16d8b122b4932c2aad05a21a22e25]
   to [5879a8268a59545c946583b65f66eda4491b979e]

patch "src/std_hooks.lua"
 from [30949d110cc1ed86d98f4f437fda16778a3e85bb]
   to [7669463d1a401dc786dbf65fc99d1a4853cf4a4f]
============================================================
--- Makefile.am	3987cf2943284d2dbe2722925395ac1544b6d1fc
+++ Makefile.am	ab8b3972917db2d7e2a0bfd42abaea31a66c143f
@@ -250,14 +250,23 @@ mtnhooksdir = $(monotonedir)/hooks
 monotonedir = $(datadir)/monotone
 
 mtnhooksdir = $(monotonedir)/hooks
-mtnhooks_DATA = \
+dist_mtnhooks_DATA = \
 	extra/mtn-hooks/authorize_remote_automate.lua			\
 	extra/mtn-hooks/get_passphrase_from_file.lua			\
+	extra/mtn-hooks/monotone-ciabot.lua				\
 	extra/mtn-hooks/monotone-mail-notify.lua
 
 mtnscriptsdir = $(monotonedir)/scripts
-dist_mtnscripts_SCRIPTS = extra/mtn-hooks/monotone-mail-notify
+dist_mtnscripts_SCRIPTS = \
+	extra/mtn-hooks/monotone-ciabot.py				\
+	extra/mtn-hooks/monotone-mail-notify
 
+# Some of the smaller programs do not have --help or --version,
+# make them exempt of the check for those.  NOTE: use this sparingly!
+AM_INSTALLCHECK_STD_OPTIONS_EXEMPT = \
+	extra/bin/mtn-cleanup 	extra/mtn-hooks/monotone-ciabot.py	\
+	extra/mtn-hooks/monotone-mail-notify
+
 # flags
 
 # sets DEFS for just this object (autoconf's -DHAVE_CONFIG_H is useless)
@@ -349,8 +358,7 @@ EXTRA_DIST =								\
 	HACKING INSTALL INSTALL_windows_cygwin.txt			\
 	INSTALL_windows_native.txt README.visualc8 UPGRADE		\
 	util/audit-includes util/do-editor-vars.sh			\
-	$(wildcard $(srcdir)/m4/*.m4) doc/monotone.html doc/images	\
-	doc/texinfo.css src/schema.sql src/unix/README			\
+	$(wildcard $(srcdir)/m4/*.m4) src/schema.sql src/unix/README	\
 	src/util/mtnopt.in innosetup/README.txt				\
 	innosetup/monotone.iss.in innosetup/modpath.iss 		\
 	innosetup/monotone.bmp innosetup/dlls.sh			\
@@ -368,7 +376,11 @@ EXTRA_DIST =								\
 									\
 	src/package_revision.txt src/package_full_revision_dist.txt	\
 									\
-	mac notes visualc $(contrib_data) $(examples_data) extra
+	mac notes visualc $(contrib_data) $(examples_data)		\
+									\
+	extra/README extra/building/dump-test-logs.sh.in		\
+	extra/shell/monotone.bash_completion.in				\
+	extra/shell/monotone_gen_bash_completion_table.pl
 
 # Message translation support
 # INST_LINGUAS is set by configure, based on the po/LINGUAS file and
@@ -463,12 +475,6 @@ dist-nls: $(ALL_GMOFILES)
 
 dist-nls: $(ALL_GMOFILES)
 	cp $(ALL_GMOFILES) $(distdir)/po
-# also, kill off any backup files that got pulled in by one of the
-# recursive subdirectory includes in EXTRA_DIST
-# note use of slightly non-portable "-delete" action
-	find $(distdir) \( -name '*.bak' -o -name '*.orig' -o -name '*.rej' \
-	    -o -name '*~' -o -name '#*#' -o -name '*.swp' \) -delete
-
 else
 
 po/%.gmo: $(srcdir)/po/%.gmo
@@ -694,7 +700,8 @@ BUILT_SOURCES = $(PCH_FILE) $(PCH_BUILD)
 
 BUILT_SOURCES = $(PCH_FILE) $(PCH_BUILD)
 
-CLEANFILES = $(bin_SCRIPTS) $(bashcomp_DATA) $(BUILT_SOURCES) $(CLEAN_SRCS) $(CLEAN_POFILES)
+CLEANFILES = $(bin_SCRIPTS) $(noinst_SCRIPTS) $(bashcomp_DATA) \
+	$(BUILT_SOURCES) $(CLEAN_SRCS) $(CLEAN_POFILES)
 
 # automake provides no nice way to build a helper program to execute
 # on the build machine, so we need our own rule.  almost all the
@@ -723,11 +730,12 @@ do_subst = sed -e 's,address@hidden
 do_subst = sed -e 's,address@hidden@],$(PACKAGE_VERSION),' \
 	-e 's,address@hidden@],$(sysconfdir),'
 
-%: src/util/%.in Makefile
+mtnopt: src/util/mtnopt.in Makefile
 	$(V_subst)$(do_subst) < $< > $@ && chmod +x $@
 
 extra/shell/monotone.bash_completion: extra/shell/monotone.bash_completion.in \
-	$(bin_PROGRAMS) extra/shell/monotone_gen_bash_completion_table.pl
+	$(bin_PROGRAMS) extra/shell/monotone_gen_bash_completion_table.pl \
+	Makefile
 	$(MKDIR_P) extra/shell
 	( sed -e '/address@hidden@]$$/,$$d' < $<; \
 	  MTN=./mtn$(EXEEXT) \
@@ -827,6 +835,7 @@ man1_MANS = mtn.1
                        src/win32/main.cc src/unix/main.cc)
 
 man1_MANS = mtn.1
+dist_man1_MANS = src/util/mtnopt.1 extra/bin/mtn-cleanup.1
 
 # All local variants of automake-supported targets here, depending on all
 # the things we want to do.  This is to support multiple things to do in
@@ -838,11 +847,17 @@ check-local: check-testers
 	uninstall-contrib-data uninstall-examples-data
 mostlyclean-local: mostlyclean-tests
 check-local: check-testers
+
 # All hooks supported by automake here, depending on all the things we want
 # to hook in.  This is to support multiple things into the same hook, as
 # dependencies.
+distcheck-hook: distcheck-base.hh
 dist-hook: dist-nls
-distcheck-hook: distcheck-base.hh
+# Kill off any backup files that got pulled in by one of the
+# recursive subdirectory includes in EXTRA_DIST
+# note use of slightly non-portable "-delete" action
+	find $(distdir) \( -name '*.bak' -o -name '*.orig' -o -name '*.rej' \
+	    -o -name '*~' -o -name '#*#' -o -name '*.swp' \) -delete
 
 mtn.1: mtn$(EXEEXT)
 	$(AM_V_GEN)REAL_BLDDIR=$$PWD/$(top_builddir); \
============================================================
--- NEWS	c99a1b030116f68845e9418fd53277c04eb53297
+++ NEWS	fa80c1f7aeaaf7278731bb1f7c7cd0a94f7b3c4b
@@ -29,6 +29,13 @@ XXX XXX XX XX:XX:XX UTC 2011
           messages from mtn automate, regardless of the locale of the
           calling process.
 
+        - The hook 'get_netsync_key' has been split up into two separate
+          hooks, one for client usage ('get_netsync_client_key', with
+          the same arguments as the original 'get_netsync_key') and one
+          for server usage ('get_netsync_server_key', with a single table
+          argument containing all the given '--bind' options).  Please
+          review your custom hooks accordingly.
+ 
         - Short options ('-b', '-d', ...) are no longer completed.  This
           fixes an invariant failure originating from wrong option usage.
           (closes monotone issue 141)
@@ -87,6 +94,19 @@ XXX XXX XX XX:XX:XX UTC 2011
           if no database is specified for these commands.
           (fixes monotone issue 113)
 
+        - If 'mtn serve' is called with one or more '--bind' options, then
+          the arguments to these options can now be specified again
+          as follows:
+
+            '<ip-or-host>' 
+                to listen to IP or host on the default port
+            '<ip-or-host>:<port>'
+                to listen to IP or host on the specified port  - or
+            ':<port>'
+                to listen on all interfaces on the specified port
+
+          (fixes monotone issue 119)
+          
         - monotone does no longer enforce ".mtn" as file extension
           for managed databases.  Instead a new Lua hook,
           get_default_database_glob(), is used to determine a pattern
============================================================
--- src/database.cc	8966518247a144a25de01b4ab8fa9e515661edfb
+++ src/database.cc	f61bd0d7d66a715c7a55cb943eb8521db6d91d68
@@ -4974,7 +4974,7 @@ database_path_helper::get_database_path(
   vector<system_path> search_paths;
 
   E(lua.hook_get_default_database_locations(search_paths) && search_paths.size() > 0,
-    origin::user, F("could not query default database locations"));
+    origin::user, F("no default database location configured"));
 
   for (vector<system_path>::const_iterator i = search_paths.begin();
      i != search_paths.end(); ++i)
============================================================
--- src/keys.cc	bdf4a6b94902d29411b16cb169851986a16b3956
+++ src/keys.cc	19db9fa939a1d87aba964f4e84743adcfff6fdd2
@@ -183,13 +183,20 @@ cache_netsync_key(options const & opts,
           found_key = true;
         }
     }
-  else if (lua.hook_get_netsync_key(utf8(info->client.get_uri().resource(), origin::user),
-                                    info->client.get_include_pattern(),
-                                    info->client.get_exclude_pattern(),
-                                    keys, project, key))
+  else if (info->info_type == netsync_connection_info::client_info &&
+           lua.hook_get_netsync_client_key(utf8(info->client.get_uri().resource(), origin::user),
+                                           info->client.get_include_pattern(),
+                                           info->client.get_exclude_pattern(),
+                                           keys, project, key))
     {
       found_key = true;
     }
+  else if (info->info_type == netsync_connection_info::server_info &&
+           lua.hook_get_netsync_server_key(info->server.addrs,
+                                           keys, project, key))
+    {
+      found_key = true;
+    }
   else
     {
       found_key = get_only_key(keys, key_requiredness, key);
============================================================
--- src/lua_hooks.cc	cd982157b1248b75f37edda84723e8944c70c5bf
+++ src/lua_hooks.cc	81dd3985d253ac62743df0341e60a74d5b93b755
@@ -798,17 +798,17 @@ bool
 }
 
 bool
-lua_hooks::hook_get_netsync_key(utf8 const & server_address,
-                                globish const & include,
-                                globish const & exclude,
-                                key_store & keys,
-                                project_t & project,
-                                key_id & k)
+lua_hooks::hook_get_netsync_client_key(utf8 const & server_address,
+                                       globish const & include,
+                                       globish const & exclude,
+                                       key_store & keys,
+                                       project_t & project,
+                                       key_id & k)
 {
   string name;
   bool exec_ok
     = Lua(st)
-    .func("get_netsync_key")
+    .func("get_netsync_client_key")
     .push_str(server_address())
     .push_str(include())
     .push_str(exclude())
@@ -827,6 +827,41 @@ lua_hooks::hook_get_netsync_key(utf8 con
     }
 }
 
+bool
+lua_hooks::hook_get_netsync_server_key(vector<utf8> const & server_ports,
+                                       key_store & keys,
+                                       project_t & project,
+                                       key_id & k)
+{
+  string name;
+  Lua ll(st);
+  ll.func("get_netsync_server_key");
+
+  ll.push_table();
+  vector<utf8>::const_iterator i;
+  int j;
+  for (i = server_ports.begin(), j = 1; i != server_ports.end(); ++i, ++j)
+    {
+      ll.push_int(j);
+      ll.push_str((*i)());
+      ll.set_table();
+    }
+
+  bool exec_ok = ll.call(1, 1)
+                   .extract_str(name)
+                   .ok();
+
+  if (!exec_ok || name.empty())
+    return false;
+  else
+    {
+      key_identity_info identity;
+      project.get_key_identity(keys, *this, external_key_name(name, origin::user), identity);
+      k = identity.id;
+      return true;
+    }
+}
+
 static void
 push_uri(uri_t const & uri, Lua & ll)
 {
============================================================
--- src/lua_hooks.hh	a7ab69e459aa1f8450ac10704f0c22a595cc69e8
+++ src/lua_hooks.hh	51a2842d22554cba640dcea416103f5e70c63c3b
@@ -80,12 +80,16 @@ public:
                                      map<key_id, bool> const & new_results);
 
   // network hooks
-  bool hook_get_netsync_key(utf8 const & server_address,
-                            globish const & include,
-                            globish const & exclude,
-                            key_store & keys,
-                            project_t & project,
-                            key_id & k);
+  bool hook_get_netsync_client_key(utf8 const & server_address,
+                                   globish const & include,
+                                   globish const & exclude,
+                                   key_store & keys,
+                                   project_t & project,
+                                   key_id & k);
+  bool hook_get_netsync_server_key(vector<utf8> const & server_ports,
+                                   key_store & keys,
+                                   project_t & project,
+                                   key_id & k);
   bool hook_get_netsync_connect_command(uri_t const & uri,
                                         globish const & include_pattern,
                                         globish const & exclude_pattern,
============================================================
--- doc/monotone.texi	8d7a3efa8e20f906edc0883785c6c8f51cc6919c
+++ doc/monotone.texi	14de7e0ea6d88053129594b1628cac25ebf14506
@@ -4687,12 +4687,15 @@ @subsection Global Options
 For a non-netsync command, the Lua hook @address@hidden is
 called.
 
-For a netsync command, the Lua hook @address@hidden is
-called.
+For a client-side netsync command, the Lua hook
address@hidden@ref{get_netsync_client_key}} is called.
 
-If the hook returns non-nil, the return value is the name of the
-key.
+For a command that starts a monotone server, the Lua hook
address@hidden@ref{get_netsync_server_key}} is used.
 
+If any of the aforementioned hooks returns non-nil, the return value
+is the name of the key.
+
 @item
 If there is only one key in the keydir, that is the key.
 
@@ -11426,12 +11429,12 @@ @subsection User Defaults
 
 There is no default definition for this hook; it returns nil.
 
address@hidden@item get_netsync_key(@var{server}, @var{include}, @var{exclude})
address@hidden@item get_netsync_client_key(@var{server}, @var{include}, @var{exclude})
 
-Called by the server and client when a netsync connection is being
-established, and @address@hidden was not given. Returns a string
-which is the name or hash of the key to use to authenticate the
-netsync connection.
+Called by the client when a netsync connection is being established,
+and @address@hidden was not given. Returns a string which is the name
+or hash of the key to use to authenticate the client side of a netsync
+connection.
 
 Note that netsync commands do not need a signing key; they only transmit
 already signed information.
@@ -11439,32 +11442,44 @@ @subsection User Defaults
 See @address@hidden for a discussion of how monotone determines
 what key to use.
 
-Arguments, when called by the server:
+Arguments, when called:
 
 @table @var
 @item server
-The address monotone is listening on.
+The scheme, user, host, port, and path fields from the URI provided on
+the command line. See @ref{netsync uri}.
 
 @item include
-"*"
+The include pattern in the URI provided on the command line.
 
 @item exclude
-""
+The exclude pattern in the URI provided on the command line.
+
 @end table
 
-When called by the client:
address@hidden@item get_netsync_server_key(@var{addresses})
 
address@hidden @var
address@hidden server
-The scheme, user, host, port, and path fields from the URI provided on
-the command line. See @ref{netsync uri}.
+Called by the server when a new netsync server instance is created, and
address@hidden@ref{--key}} was not given. Returns a string which is the name
+or hash of the key to use to authenticate the server side of a netsync
+connection.
 
address@hidden include
-The include pattern in the URI provided on the command line.
+Note that netsync commands do not need a signing key; they only transmit
+already signed information.
 
address@hidden exclude
-The exclude pattern in the URI provided on the command line.
+See @address@hidden for a discussion of how monotone determines
+what key to use.
 
+Arguments, when called:
+
address@hidden @var
address@hidden addresses
+A table of addresses given to monotone via the @option{--bind} option that
+denote the addresses and / or ports monotone is listening on for connections.
+If the address in one of the table entries is omitted, the port must be given
+with a leading colon; in this case monotone listens on all interfaces on this
+port.
+
 @end table
 
 @address@hidden get_default_command_options(@var{command})
============================================================
--- .mtn-ignore	ee776b54b58b1d3cc4265cdfa09a6d426ae1f6c5
+++ .mtn-ignore	347361ce3fa47016cbe4f5ba29188696d939a1d8
@@ -16,7 +16,8 @@
 ^doc/stamp-vti$
 ^doc/texinfo\.tex$
 ^doc/version\.texi$
-^html
+^extra/building/dump-test-logs.sh$
+^extra/shell/monotone.bash_completion$
 ^install-sh$
 ^m4/codeset\.m4$
 ^m4/gettext\.m4$
@@ -55,7 +56,7 @@
 ^po/insert-header\.sin$
 ^po/monotone\.pot$
 ^po/quot\.sed$
-^run_(func|tester|unit)_tests$
+^run_(extra|func|tester|unit)_tests$
 ^src/package_(full_)?revision(\.cc|\.txt|_(dist|raw)\.txt)$
 ^src/schema\.cc$
 ^src/std_hooks\.cc$
============================================================
--- src/cmd_list.cc	12643b2161237863a8de6656c99328dc6ea3c7e6
+++ src/cmd_list.cc	eede6b49bfe267b8e9b361c12bbcc26f53e86191
@@ -681,7 +681,7 @@ CMD(databases, "databases", "dbs", CMD_R
   vector<system_path> search_paths, files, dirs;
 
   E(app.lua.hook_get_default_database_locations(search_paths), origin::user,
-    F("could not query default database locations"));
+    F("no default database location configured"));
 
   globish file_matcher;
   E(app.lua.hook_get_default_database_glob(file_matcher), origin::user,
@@ -736,8 +736,8 @@ CMD(databases, "databases", "dbs", CMD_R
               continue;
             }
 
-            cout << F("%s (in %s):") % db_alias % search_path << "\n";
-            print_workspace_info(db, app.lua, cout, "\t");
+          cout << F("%s (in %s):") % db_alias % search_path << "\n";
+          print_workspace_info(db, app.lua, cout, "\t");
         }
     }
 }
============================================================
--- test/func/netsync_key_hook/__driver__.lua	87963f895052ee9dd35d513e23004811169bca38
+++ test/func/netsync_key_hooks/__driver__.lua	d7fad5a70688d3db512a05ebb0e3643006f8c7e2
@@ -1,6 +1,10 @@ mtn_setup()
 
 includecommon("netsync.lua")
 mtn_setup()
+
+--
+-- test get_netsync_client_key
+--
 netsync.setup()
 
 writefile("foo", "bar")
@@ -21,6 +25,7 @@ function client(what, ret)
      "--keydir=keys",
      "--db=test.db", srv.address,
      "--rcfile=client-hooks.lua",
+     "--no-workspace",
      "*"}
   for k, v in pairs(args) do
      table.insert(what, v)
@@ -34,3 +39,33 @@ srv:stop()
 client({"pull", "address@hidden"}, 1)
 
 srv:stop()
+
+--
+-- test get_netsync_server_key
+--
+
+get("server-hooks.lua")
+-- we send a SIGTERM to the server process to stop it, so this is also
+-- what we expect as (negated) return value
+SIGTERM=15
+
+function server(what, ret, exp_err)
+  local addr = "localhost:" .. math.random(1024, 65535)
+  args = {"--rcfile=test_hooks.lua",
+     "--keydir=keys",
+     "--db=test.db", "--bind=" .. addr,
+     "--no-workspace",
+     "serve"}
+  for k, v in pairs(args) do
+     table.insert(what, v)
+  end
+  srv = bg(raw_mtn(unpack(what)), ret, false, true)
+  srv:finish(3)
+  if exp_err ~= nil then
+    check(qgrep(exp_err, "stderr"))
+  end
+end
+
+server({}, 1, "you have multiple private keys")
+server({"--rcfile", "server-hooks.lua"}, -SIGTERM, "beginning service on localhost")
+
============================================================
--- test/func/netsync_key_hook/client-hooks.lua	f60d553a6bff838e53904f94294588d9f9ed84ee
+++ test/func/netsync_key_hooks/client-hooks.lua	e9eb38666d23a1f4a5be33404220902ee90756eb
@@ -1,3 +1,3 @@
-function get_netsync_key(server, include, exclude)
+function get_netsync_client_key(server, include, exclude)
    return "address@hidden"
 end
============================================================
--- contrib/monotone-cluster-push.lua	2ee0e849058ea7bbef9487fc21772d1fb66500d6
+++ contrib/monotone-cluster-push.lua	3a4f3bd0394d70d5c07c9314ad42026b212a4983
@@ -210,7 +210,7 @@ do
 	    end
 	    if certs_in > 0 or revs_in > 0 or keys_in > 0 then
 	       local pattern_branches =
-		  process_rcfile("not_netsync_end", MCP_rcfile, nil)
+		  process_rcfile("note_netsync_end", MCP_rcfile, nil)
 	       if pattern_branches then
 		  for pattern, servers in pairs(pattern_branches) do
 		     if globish_match(pattern, branch) then
@@ -222,7 +222,7 @@ do
 			   io.stderr:write("note_netsync_end: ",
 					   "pushing pattern \"", pattern,
 					   "\" to server ", server, "\n")
-			   server_request_sync("push", server, pattern, "")
+			   server_request_sync("push", server.."?"..pattern, "")
 			end
 		     end
 		  end
@@ -232,7 +232,5 @@ do
 	 end
    }
 
-   local saved_note_mtn_startup = note_mtn_startup
-
    push_netsync_notifier(notifier)
 end
============================================================
--- src/network/connection_info.cc	78789566ad076c92d8795ea7e3877646c3749693
+++ src/network/connection_info.cc	b732c16161db2394aac449f50c6a6921e553ef22
@@ -279,7 +279,7 @@ netsync_connection_info::Client::set_raw
 
   if (uri.scheme.empty())
     uri.scheme = "mtn";
- 
+
   E(uri.scheme != "mtn" || !uri.host.empty(), origin::user,
     F("a non-empty hostname is expected for the 'mtn' uri scheme"));
 
@@ -400,6 +400,7 @@ netsync_connection_info::setup_default(o
                                        shared_conn_info & info)
 {
   info.reset(new netsync_connection_info(db, opts));
+  info->info_type = client_info;
   info->client.conn_type = type;
 
   info->client.ensure_completeness();
@@ -414,6 +415,7 @@ netsync_connection_info::setup_from_sync
                                                  shared_conn_info & info)
 {
   info.reset(new netsync_connection_info(db, opts));
+  info->info_type = client_info;
   info->client.conn_type = netsync_connection;
 
   info->client.set_raw_uri(request.address);
@@ -461,6 +463,7 @@ netsync_connection_info::setup_from_uri(
                                         shared_conn_info & info)
 {
   info.reset(new netsync_connection_info(db, opts));
+  info->info_type = client_info;
   info->client.conn_type = type;
 
   info->client.set_raw_uri(uri());
@@ -497,6 +500,7 @@ netsync_connection_info::setup_from_serv
       "please consider using the URI calling syntax instead"));
 
   info.reset(new netsync_connection_info(db, opts));
+  info->info_type = client_info;
   info->client.conn_type = type;
 
   info->client.set_raw_uri(host());
@@ -515,6 +519,7 @@ netsync_connection_info::setup_for_serve
                                          shared_conn_info & info)
 {
   info.reset(new netsync_connection_info(db, opts));
+  info->info_type = server_info;
   info->server.addrs = opts.bind_uris;
   info->client.conn_type = netsync_connection;
 
@@ -523,16 +528,6 @@ netsync_connection_info::setup_for_serve
       E(lua.hook_persist_phrase_ok(), origin::user,
         F("need permission to store persistent passphrase "
           "(see hook persist_phrase_ok())"));
-
-      // the uri as well as the include / exclude pattern are
-      // not used directly for serve, but need to be configured
-      // in order to let keys::cache_netsync_key() call the
-      // get_netsync_key() hook properly
-      if (!opts.bind_uris.empty())
-        info->client.set_raw_uri((*opts.bind_uris.begin())());
-
-      info->client.include_pattern = globish("*", origin::internal);
-      info->client.exclude_pattern = globish("", origin::internal);
     }
   else if (!opts.bind_stdio)
     W(F("The --no-transport-auth option is usually only used "
============================================================
--- src/network/connection_info.hh	8841c299c1a214c46071b6f77c3a5dbadeaf4959
+++ src/network/connection_info.hh	0ba5fc1778b78fb3446ad345ec78e073fd9221e4
@@ -102,6 +102,8 @@ struct netsync_connection_info
 
 struct netsync_connection_info
 {
+  enum { client_info, server_info } info_type;
+
   class Server
   {
   public:
============================================================
--- test/func/manpage/__driver__.lua	2230d26ba35875be86cbb8000b4c0c8c7d36f735
+++ test/func/manpage/__driver__.lua	a381ef4517c907170547a4984923650617f69c60
@@ -6,7 +6,7 @@ local s,e,version = string.find(readfile
 -- check for a proper header line
 check(mtn("version"), 0, true, false)
 local s,e,version = string.find(readfile("stdout"), "(monotone %d+\.%d+%S*)")
-check(qgrep(".TH \"monotone\" 1 \"" .. os.date("%Y-%m-%d") .. "\" \"" .. version .. "\"", "manpage"))
+check(qgrep(".TH \"monotone\" 1 \"[0-9]{4}-[0-9]{2}-[0-9]{2}\" \"" .. version .. "\"", "manpage"))
 
 -- check required sections
 check(qgrep(".SH \"NAME\"", "manpage"))
@@ -20,6 +20,3 @@ check(qgrep(".SH \"COPYRIGHT\"", "manpag
 check(qgrep(".SH \"BUGS\"", "manpage"))
 check(qgrep(".SH \"AUTHORS\"", "manpage"))
 check(qgrep(".SH \"COPYRIGHT\"", "manpage"))
-
--- ensure that the copyright is up-to-date
-check(qgrep("Copyright [(]c[)] 2004 - "..os.date("%Y"), "manpage"))
============================================================
--- extra/shell/monotone.bash_completion.in	f7d35f2c13c6babc009800a11f6eff1eb69b01d9
+++ extra/shell/monotone.bash_completion.in	a878e6fa815f84f18961fb2366d0235376be8c29
@@ -281,9 +281,10 @@ _monotone() {
 		elif [ $arg_cword -ge 0 ]; then
 		    local args=( ${_monotone_command_args[$cmd_line]} )
 		    local argtype=${args[$arg_cword]}
-		    if [ "$argtype" = "..." ]; then
+		    if [ $arg_cword -ge 1 ] && [ "$argtype" = "..." ]; then
 			argtype=${args[$arg_cword-1]}
-		    elif [ -z "$argtype" -a "${args[${#args[*]}-1]}" = "..." ]; then
+		    elif [ ${#args[*]} -ge 2 ] && \
+			[ -z "$argtype" -a "${args[${#args[*]}-1]}" = "..." ]; then
 			argtype=${args[${#args[*]}-2]}
 		    fi
 		    case $argtype in
============================================================
--- test/extra/mtn-cleanup/__driver__.lua	c0a63dd6bbb69d30db6f6b64f7d59f2b5f1d00c7
+++ test/extra/mtn-cleanup/__driver__.lua	f650305a0d130a4ac6fb988eed58662a564dcc64
@@ -17,7 +17,7 @@ writefile("workspace/test3", "baz")
 check(indir("workspace", mtn("add", "test2")), 0, false, false)
 writefile("workspace/test3", "baz")
 
-check(indir("workspace", {"./run-mtn-cleanup",srcdir,test.root}),
+check(indir("workspace", {"../run-mtn-cleanup",srcdir,test.root}),
       0, true, false)
 check(exists("workspace/test1"))
 xfail(exists("workspace/test2"))
============================================================
--- /dev/null	
+++ test/func/netsync_bind_opt/__driver__.lua	153c2be3d3c3c37b4fea590633d2d3be9bae5e82
@@ -0,0 +1,27 @@
+skip_if(not existsonpath("netstat"))
+
+includecommon("netsync.lua")
+mtn_setup()
+netsync.setup()
+
+math.randomseed(get_pid())
+local port = math.random(1024, 65535)
+
+-- test with host:port
+srv = netsync.start({"--bind", "localhost:" .. port})
+check({"netstat", "-a", "-n"}, 0, true, false)
+check(qgrep("127[.]0[.]0[.]1[.:]" .. port, "stdout"))
+srv:stop()
+
+-- test with ip:port
+srv = netsync.start({"--bind", "127.0.0.1:" .. port})
+check({"netstat", "-a", "-n"}, 0, true, false)
+check(qgrep("127[.]0[.]0[.]1[.:]" .. port, "stdout"))
+srv:stop()
+
+-- test only with :port
+srv = netsync.start({"--bind", ":" .. port})
+check({"netstat", "-a", "-n"}, 0, true, false)
+check(qgrep("([*]|0[.]0[.]0[.]0)[.:]" .. port, "stdout"))
+srv:stop()
+
============================================================
--- /dev/null	
+++ test/func/netsync_key_hooks/server-hooks.lua	434dab628a58b2c644f5ad6997420f1186cabbb5
@@ -0,0 +1,3 @@
+function get_netsync_server_key(addresses)
+   return "address@hidden"
+end
============================================================
--- /dev/null	
+++ extra/bin/mtn-cleanup.1	b898678b0775bf10a7428cfd8b52a4883edddbcc
@@ -0,0 +1,22 @@
+.TH "MTN-CLEANUP" 1 2011-02-22 monotone monotone
+.SH "NAME"
+mtn-cleanup \- generate shell variables from monotone workspace options
+.SH "SYNOPSIS"
+.B mtn-cleanup
+.br
+.SH "DESCRIPTION"
+.B mtn-cleanup
+returns a workspace to its pristine state with the minimum of change;
+missing files are restored, changed files are reverted and new files
+are removed.
+.SH "BUGS"
+.B mtn-cleanup
+doesn't look to see if the database happens to be somewhere in the
+workspace, and might therefore remove it.  Future version may take a
+look at
+.I _MTN/options
+to check for this possibility.
+.SH "SEE ALSO"
+.BR mtn (1),
+.SH "AUTHOR"
+Written by Anthony Edward Cooper <address@hidden>.
============================================================
--- /dev/null	
+++ src/util/mtnopt.1	8e24a1cafa100a7d6681cc19e30ee82a4cf517f4
@@ -0,0 +1,95 @@
+.TH MTNOPT 1 2011-02-22 monotone monotone
+.SH NAME
+mtnopt \- generate shell variables from monotone workspace options
+.SH SYNOPSIS
+.B mtnopt
+.RB [\| \-s \||\| \-c \||\| \-v \|]
+.RB [\| \-d 
+.IR dir \|]
+.RB [\| \-k
+.IR keys \|]
+.br
+.B mtnopt -h
+.br
+.SH DESCRIPTION
+.B mtnopt
+prints shell variable assignments for each value in the file of
+monotone workspace options,
+.IR _MTN/options ,
+in the current directory. For instance, if
+.I _MTN/options
+contained this text:
+.IP
+\f(CWdatabase "/home/user/src/monotone.mtn"\fP
+.br
+\f(CW  branch "net.venge.monotone"\fP
+.br
+\f(CW  keydir "/home/user/.monotone/keys"\fP
+.PP
+.B mtnopt
+would print:
+.IP
+\f(CWMTN_database="/home/user/src/monotone.mtn";\fP
+.br
+\f(CWMTN_branch="net.venge.monotone";\fP
+.br
+\f(CWMTN_keydir="/home/user/.monotone/keys";\fP
+.PP
+By default,
+.B mtnopt 
+attempts to guess appropriate syntax from the value of the
+.B SHELL
+environment variable.  This can be overridden with the 
+.B \-s
+and 
+.B \-c
+command\(hyline options.
+.SH OPTIONS
+.TP
+.B \-s
+Print variable assignments in 
+.BR sh (1)
+syntax.
+.TP
+.B \-c
+Print variable assignments in 
+.BR csh (1)
+syntax.
+.TP
+.B \-v
+Print only the values, with no indication of the corresponding option keys.
+.TP
+.BI \-d\  directory
+Look for 
+.I _MTN/options
+in
+.IR directory ,
+rather than in the current directory.
+.TP
+.BI \-k\  keys
+Print assignments for only those options that match the
+.BR egrep (1)
+regular _expression_
+.IR keys .
+.TP
+.B \-h
+Print a help message and exit.
+.SH BUGS
+.B mtnopt
+only looks in the current directory for the
+.I _MTN
+directory, so it will fail in a subdirectory of a workspace.
+.PP
+.B mtnopt
+should be aware of the set of possible options, rather than blindly
+printing whatever is in
+.IR _MTN/options .
+.PP
+The behavior when there is no
+.I _MTN/options
+file to be found is less than helpful.
+.SH SEE ALSO
+.BR mtn (1),
+.BR egrep (1),
+.BR sh (1),
+.BR csh (1)
============================================================
--- Makefile.am	6e6a3f7aaecccc749e261d452d84bd8431ba6cb7
+++ Makefile.am	ab8b3972917db2d7e2a0bfd42abaea31a66c143f
@@ -47,6 +47,7 @@ MOST_SOURCES = 								\
 	src/automate_reader.cc src/automate_stdio_helpers.hh		\
 	src/botan_pipe_cache.hh	src/cache_logger.hh src/cache_logger.cc	\
 	src/commands.cc src/commands.hh $(CMD_SOURCES)			\
+	src/colorizer.cc src/colorizer.hh				\
 	src/diff_output.cc src/diff_output.hh				\
 	src/lua_hooks.cc src/lua_hooks.hh 				\
 	src/transforms.cc src/transforms.hh				\
============================================================
--- NEWS	848a8d23c760b709d5954ef1ca4a812c72404343
+++ NEWS	fa80c1f7aeaaf7278731bb1f7c7cd0a94f7b3c4b
@@ -392,6 +392,11 @@ Thu Oct 28 21:07:18 UTC 2010
         - New 'k:' selector type to query revisions where at least one
           certificate was signed with the given key.
 
+        - Monotone has a new global '--colorize' option which colors the
+          output of commands like 'diff', 'fdiff' or 'log' for better
+          readability in terminals that support output coloring and
+          formatting.
+
         - New automate command 'log' which behaves identical to the
           normal 'log' command, except that it only outputs the
           revision ids.
============================================================
--- src/diff_output.cc	8df7c0bec2e64275f6c8e8b6ccb035c8fd0f684f
+++ src/diff_output.cc	db19431f96f35ca8c6b89c129c2a805ddd418f15
@@ -17,6 +17,7 @@
 #include "simplestring_xform.hh"
 
 #include <ostream>
+#include <sstream>
 #include <iterator>
 #include <boost/scoped_ptr.hpp>
 
@@ -25,6 +26,7 @@ using std::string;
 using std::ostream;
 using std::ostream_iterator;
 using std::string;
+using std::stringstream;
 using std::vector;
 using boost::scoped_ptr;
 
@@ -45,6 +47,8 @@ struct hunk_consumer
   vector<string>::const_reverse_iterator encloser_last_match;
   vector<string>::const_reverse_iterator encloser_last_search;
 
+  colorizer color;
+
   virtual void flush_hunk(size_t pos) = 0;
   virtual void advance_to(size_t newpos) = 0;
   virtual void insert_at(size_t b_pos) = 0;
@@ -55,10 +59,12 @@ struct hunk_consumer
                 vector<string> const & b,
                 size_t ctx,
                 ostream & ost,
-                string const & encloser_pattern)
+                string const & encloser_pattern,
+                colorizer const & color)
     : a(a), b(b), ctx(ctx), ost(ost), encloser_re(0),
       a_begin(0), b_begin(0), a_len(0), b_len(0), skew(0),
-      encloser_last_match(a.rend()), encloser_last_search(a.rend())
+      encloser_last_match(a.rend()), encloser_last_search(a.rend()),
+      color(color)
   {
     if (encloser_pattern != "")
       encloser_re.reset(new pcre::regex(encloser_pattern, origin::user));
@@ -170,21 +176,24 @@ struct unidiff_hunk_writer : public hunk
                       vector<string> const & b,
                       size_t ctx,
                       ostream & ost,
-                      string const & encloser_pattern)
-  : hunk_consumer(a, b, ctx, ost, encloser_pattern)
+                      string const & encloser_pattern,
+                      colorizer const & color)
+  : hunk_consumer(a, b, ctx, ost, encloser_pattern, color)
   {}
 };
 
 void unidiff_hunk_writer::insert_at(size_t b_pos)
 {
   b_len++;
-  hunk.push_back(string("+") + b[b_pos]);
+  hunk.push_back(color.colorize(string("+") + b[b_pos],
+                                    colorizer::add));
 }
 
 void unidiff_hunk_writer::delete_at(size_t a_pos)
 {
   a_len++;
-  hunk.push_back(string("-") + a[a_pos]);
+  hunk.push_back(color.colorize(string("-") + a[a_pos],
+                                    colorizer::remove));
 }
 
 void unidiff_hunk_writer::flush_hunk(size_t pos)
@@ -201,22 +210,23 @@ void unidiff_hunk_writer::flush_hunk(siz
         }
 
       // write hunk to stream
+      stringstream ss;
       if (a_len == 0)
-        ost << "@@ -0,0";
+        ss << "@@ -0,0";
       else
         {
-          ost << "@@ -" << a_begin+1;
+          ss << "@@ -" << a_begin+1;
           if (a_len > 1)
-            ost << ',' << a_len;
+            ss << ',' << a_len;
         }
-
+ 
       if (b_len == 0)
-        ost << " +0,0";
+        ss << " +0,0";
       else
         {
-          ost << " +" << b_begin+1;
+          ss << " +" << b_begin+1;
           if (b_len > 1)
-            ost << ',' << b_len;
+            ss << ',' << b_len;
         }
 
       {
@@ -231,7 +241,11 @@ void unidiff_hunk_writer::flush_hunk(siz
             }
 
         find_encloser(a_begin + first_mod, encloser);
-        ost << " @@" << encloser << '\n';
+        ss << " @@";
+
+        ost << color.colorize(ss.str(), colorizer::separator);
+        ost << color.colorize(encloser, colorizer::encloser);
+        ost << '\n';
       }
       copy(hunk.begin(), hunk.end(), ostream_iterator<string>(ost, "\n"));
     }
@@ -297,8 +311,9 @@ struct cxtdiff_hunk_writer : public hunk
                       vector<string> const & b,
                       size_t ctx,
                       ostream & ost,
-                      string const & encloser_pattern)
-  : hunk_consumer(a, b, ctx, ost, encloser_pattern),
+                      string const & encloser_pattern,
+                      colorizer const & colorizer)
+  : hunk_consumer(a, b, ctx, ost, encloser_pattern, colorizer),
     have_insertions(false), have_deletions(false)
   {}
 };
@@ -360,7 +375,8 @@ void cxtdiff_hunk_writer::flush_hunk(siz
         find_encloser(a_begin + min(first_insert, first_delete),
                       encloser);
 
-        ost << "***************" << encloser << '\n';
+        ost << color.colorize("***************", colorizer::separator)
+            << color.colorize(encloser, colorizer::encloser) << '\n';
       }
 
       ost << "*** " << (a_begin + 1) << ',' << (a_begin + a_len) << " ****\n";
@@ -394,23 +410,33 @@ void cxtdiff_hunk_writer::flush_pending_
 
   // if we have just insertions to flush, prefix them with "+"; if
   // just deletions, prefix with "-"; if both, prefix with "!"
+  colorizer::purpose p = colorizer::normal;
   if (inserts.empty() && !deletes.empty())
+  {
     prefix = "-";
+    p = colorizer::remove;
+  }
   else if (deletes.empty() && !inserts.empty())
+  {
     prefix = "+";
+    p = colorizer::add;
+  }
   else
+  {
     prefix = "!";
+    p = colorizer::change;
+  }
 
   for (vector<size_t>::const_iterator i = deletes.begin();
        i != deletes.end(); ++i)
     {
-      from_file.push_back(prefix + string(" ") + a[*i]);
+      from_file.push_back(color.colorize(prefix + string(" ") + a[*i], p));
       a_len++;
     }
   for (vector<size_t>::const_iterator i = inserts.begin();
        i != inserts.end(); ++i)
     {
-      to_file.push_back(prefix + string(" ") + b[*i]);
+      to_file.push_back(color.colorize(prefix + string(" ") + b[*i], p));
       b_len++;
     }
 
@@ -471,16 +497,19 @@ make_diff(string const & filename1,
           data const & data2,
           ostream & ost,
           diff_type type,
-          string const & pattern)
+          string const & pattern,
+          colorizer const & color)
 {
   if (guess_binary(data1()) || guess_binary(data2()))
     {
       // If a file has been removed, filename2 will be "/dev/null".
       // It doesn't make sense to output that.
       if (filename2 == "/dev/null")
-        ost << "# " << filename1 << " is binary\n";
+        ost << color.colorize(string("# ") + filename1 + " is binary",
+                              colorizer::comment) << "\n";
       else
-        ost << "# " << filename2 << " is binary\n";
+        ost << color.colorize(string("# ") + filename2 + " is binary",
+                              colorizer::comment) << "\n";
       return;
     }
 
@@ -569,23 +598,27 @@ make_diff(string const & filename1,
     {
       case unified_diff:
       {
-        ost << "--- " << filename1 << '\t'
-            << id1 << '\n';
-        ost << "+++ " << filename2 << '\t'
-            << id2 << '\n';
+        ost << color.colorize(string("--- ") + filename1,
+                              colorizer::remove)
+            << '\t' << id1 << '\n';
+        ost << color.colorize(string("+++ ") + filename2,
+                              colorizer::add)
+            << '\t' << id2 << '\n';
 
-        unidiff_hunk_writer hunks(lines1, lines2, 3, ost, pattern);
+        unidiff_hunk_writer hunks(lines1, lines2, 3, ost, pattern, color);
         walk_hunk_consumer(lcs, left_interned, right_interned, hunks);
         break;
       }
       case context_diff:
       {
-        ost << "*** " << filename1 << '\t'
-            << id1 << '\n';
-        ost << "--- " << filename2 << '\t'
-            << id2 << '\n';
+        ost << color.colorize(string("*** ") + filename1,
+                              colorizer::remove)
+            << '\t' << id1 << '\n';
+        ost << color.colorize(string("--- ") + filename2,
+                              colorizer::add)
+            << '\t' << id2 << '\n';
 
-        cxtdiff_hunk_writer hunks(lines1, lines2, 3, ost, pattern);
+        cxtdiff_hunk_writer hunks(lines1, lines2, 3, ost, pattern, color);
         walk_hunk_consumer(lcs, left_interned, right_interned, hunks);
         break;
       }
============================================================
--- src/diff_output.hh	9125ccd0d0fa725782c9910b5f34e844048d2da8
+++ src/diff_output.hh	168281addd27cdade1dc1320ee4814b28070feb8
@@ -15,6 +15,7 @@
 // of GNU-diffutils-like things (diff, diff3, maybe patch..)
 
 #include "vocab.hh"
+#include "colorizer.hh"
 
 void make_diff(std::string const & filename1,
                std::string const & filename2,
@@ -24,7 +25,8 @@ void make_diff(std::string const & filen
                data const & data2,
                std::ostream & ost,
                diff_type type,
-               std::string const & pattern);
+               std::string const & pattern,
+               colorizer const & colorizer);
 
 #endif // __DIFF_PATCH_HH__
 
============================================================
--- src/lua_hooks.cc	1c178085332c73dcefb4681d205d17b059e52080
+++ src/lua_hooks.cc	81dd3985d253ac62743df0341e60a74d5b93b755
@@ -770,6 +770,21 @@ bool
 }
 
 bool
+lua_hooks::hook_get_output_color(string const purpose, string & fg,
+                                 string & bg, string & style)
+{
+  Lua ll = Lua(st);
+
+  return ll.func("get_output_color")
+    .push_str(purpose)
+    .call(1, 3)
+    .extract_str(style).pop()
+    .extract_str(bg).pop()
+    .extract_str(fg)
+    .ok();
+}
+
+bool
 lua_hooks::hook_use_inodeprints()
 {
   bool use = false, exec_ok = false;
============================================================
--- src/lua_hooks.hh	66412b9fa5db97cd3b3ec01cadf036fb346ac161
+++ src/lua_hooks.hh	51a2842d22554cba640dcea416103f5e70c63c3b
@@ -173,6 +173,9 @@ public:
 
   bool hook_get_man_page_formatter_command(string & command);
 
+  bool hook_get_output_color(string const purpose, string & fg,
+                             string & bg, string & style);
+
   // notification hooks
   bool hook_note_commit(revision_id const & new_id,
                         revision_data const & rdat,
============================================================
--- doc/monotone.texi	5c18cff828089e592f5f2d53c2d60793715a4a40
+++ doc/monotone.texi	14de7e0ea6d88053129594b1628cac25ebf14506
@@ -5858,7 +5858,7 @@ @section Informative
 @code{fa36}. This command is intended to be used by programmable
 completion systems, such as those in @command{bash} and @command{zsh}.
 
address@hidden address@hidden mtn diff [--unified] [--[no-]show-encloser]
address@hidden address@hidden mtn diff [--unified] [--[no-]show-encloser] [--[no-]colorize]
 @itemx mtn diff --context [--[no-]show-encloser]
 @itemx mtn diff --external address@hidden
 @itemx mtn diff @var{pathname...}
@@ -5900,7 +5900,7 @@ @section Informative
 changed within the current subdirectory of the workspace.
 
 The output format of @command{diff} is controlled by the options
address@hidden, @option{--context}, @option{--no-show-encloser}, and
address@hidden, @option{--context}, @option{--no-show-encloser}, @option{--colorize}, and
 @option{--external}.  By default, monotone uses its built-in diff
 algorithm to produce a listing in ``unified diff'' format (analogous
 to running the program @command{diff @option{-u}}); you can also explicitly
@@ -5922,6 +5922,10 @@ @section Informative
 @ref{get_encloser_pattern}.  For the regular _expression_ syntax, see
 @ref{Regexps}.
 
+Furthermore, when @option{--colorize} is given, monotone tries to print 
+colored diff output if the underlying terminal supports it.  This works
+in both modes as well.
+
 Sometimes, you may want more flexibility in output formats; for these
 cases, you can use @option{--external}, which causes monotone to
 invoke an external program to generate the actual output.  By default,
@@ -6149,7 +6153,7 @@ @section Informative
 within this list.  See @ref{Managed Databases} for more information.
 
 @anchor{mtn address@hidden mtn log
address@hidden mtn log address@hidden address@hidden address@hidden [...]] [--clear-from] address@hidden [...]] [--clear-to] address@hidden [...]] [--[no-]brief] [--[no-]merges] [--[no-]files] [--[no-]graph] [--[no-]diffs] address@hidden
address@hidden mtn log address@hidden address@hidden address@hidden [...]] [--clear-from] address@hidden [...]] [--clear-to] address@hidden [...]] [--[no-]brief] [--[no-]merges] [--[no-]files] [--[no-]graph] [--[no-]diffs] [--[no-]colorize] address@hidden
 See the online help for more options.
 
 This command prints out a log, in forward ancestry order by default
@@ -6220,8 +6224,11 @@ @section Informative
 prefix on log output lines.
 
 Specifying @option{--diffs} causes the log output to include a unified
-diff of the changes in each revision.
+diff of the changes in each revision. If @option{--colorize} is given
+additionally, the diff output is colored if the underlying terminal
+supports that.
 
+
 If one or more files are given, the command will only log the revisions
 where those files are changed.
 
============================================================
--- src/std_hooks.lua	30949d110cc1ed86d98f4f437fda16778a3e85bb
+++ src/std_hooks.lua	7669463d1a401dc786dbf65fc99d1a4853cf4a4f
@@ -1532,3 +1532,41 @@ end
    end
 end
 
+function get_output_color(purpose)
+	-- Returns a triple containing the fore color, background color and
+	-- style to use for formatting the output.
+	-- The fore color and background color can be any of the following
+	-- red, green, blue, yellow, cyan, magenta, black, white
+	-- Alternatively, they can be the empty string and Monotone will
+	-- decide.
+	-- Valid values for style are
+	-- none, bold, italic, underline
+	-- Alternatively, it can be the empty string and Monotone will
+	-- decide.
+
+	local default_color = { fg = "", bg = "", style = "" }
+	local color_table = 
+	{
+		normal = default_color,
+		
+                add = { fg = "green", bg = "", style = "" },
+                change = { fg = "blue", bg = "", style = "" },
+                comment = { fg = "yellow", bg = "", style = "" },
+                encloser = { fg = "magenta", bg = "", style = "" },
+                log_revision = { fg = "", bg = "", style = "bold" },
+                remove = { fg = "red", bg = "", style = "" },
+                rename = { fg = "yellow", bg = "", style = "" },
+                rev_header = { fg = "", bg = "", style = "bold" },
+                separator = { fg = "", bg = "", style = "bold" },
+                set = { fg = "cyan", bg = "", style = "" },
+                unset = { fg = "magenta", bg = "", style = "" }
+	}
+
+	local chosen_color = color_table[purpose]
+	
+	if chosen_color == nil then
+		return default_color
+	else
+		return chosen_color.fg, chosen_color.bg, chosen_color.style
+	end
+end
============================================================
--- src/cmd_diff_log.cc	94a354875b587d372e6d0898ab6fc8355ce29c21
+++ src/cmd_diff_log.cc	c8d5d47eecd08a5935b6739b640313effdfae100
@@ -17,6 +17,7 @@
 #include "asciik.hh"
 #include "charset.hh"
 #include "cmd.hh"
+#include "colorizer.hh"
 #include "date_format.hh"
 #include "diff_output.hh"
 #include "file_io.hh"
@@ -70,6 +71,7 @@ dump_diff(lua_hooks & lua,
           bool external_diff_args_given,
           string external_diff_args,
           string const & encloser,
+          colorizer const & colorizer,
           ostream & output)
 {
   if (diff_format == external_diff)
@@ -111,7 +113,8 @@ dump_diff(lua_hooks & lua,
       make_diff(left, right,
                 left_id, right_id,
                 left_data, right_data,
-                output, diff_format, encloser);
+                output, diff_format,
+                encloser, colorizer);
     }
 
 }
@@ -134,7 +137,8 @@ dump_diffs(lua_hooks & lua,
            string external_diff_args,
            bool left_from_db,
            bool right_from_db,
-           bool show_encloser)
+           bool show_encloser,
+           colorizer const & colorizer)
 {
   // Put all node data in a multimap with the file path of the node as key
   // which gets automatically sorted. For removed nodes the file path is
@@ -220,7 +224,7 @@ dump_diffs(lua_hooks & lua,
                 dat.left_id, dat.right_id,
                 left_data, right_data,
                 diff_format, external_diff_args_given, external_diff_args,
-                encloser, output);
+                encloser, colorizer, output);
     }
 }
 
@@ -382,6 +386,7 @@ void dump_header(std::string const & rev
                  roster_t const & old_roster,
                  roster_t const & new_roster,
                  std::ostream & out,
+                 colorizer const & colorizer,
                  bool show_if_empty)
 {
   cset changes;
@@ -394,19 +399,23 @@ void dump_header(std::string const & rev
 
   vector<string> lines;
   split_into_lines(summary(), lines);
-  out << "#\n";
+  out << colorizer.colorize("#", colorizer::comment) << "\n";
   if (!summary().empty())
     {
-      out << revs << "#\n";
+      out << colorizer.colorize(revs, colorizer::comment);
+      out << colorizer.colorize("#", colorizer::comment) << "\n";
+
       for (vector<string>::iterator i = lines.begin();
            i != lines.end(); ++i)
-        out << "# " << *i << '\n';
+        out << colorizer.colorize(string("# ") + *i,
+                                  colorizer::comment) << "\n";
     }
   else
     {
-      out << "# " << _("no changes") << '\n';
+      out << colorizer.colorize(string("# ") + _("no changes"),
+                                colorizer::comment) << "\n";
     }
-  out << "#\n";
+  out << colorizer.colorize("#", colorizer::comment) << "\n";
 }
 
 CMD_PRESET_OPTIONS(diff)
@@ -437,9 +446,11 @@ CMD(diff, "diff", "di", CMD_REF(informat
 
   prepare_diff(app, db, old_roster, new_roster, args, old_from_db, new_from_db, revs);
 
+  colorizer colorizer(app.opts.colorize, app.lua);
+
   if (app.opts.with_header)
     {
-      dump_header(revs, old_roster, new_roster, cout, true);
+      dump_header(revs, old_roster, new_roster, cout, colorizer, true);
     }
 
   dump_diffs(app.lua, db, old_roster, new_roster, cout,
@@ -447,7 +458,8 @@ CMD(diff, "diff", "di", CMD_REF(informat
              app.opts.external_diff_args_given,
              app.opts.external_diff_args,
              old_from_db, new_from_db,
-             !app.opts.no_show_encloser);
+             !app.opts.no_show_encloser,
+             colorizer);
 }
 
 
@@ -476,16 +488,20 @@ CMD_AUTOMATE(content_diff, N_("[FILE [..
   prepare_diff(app, db, old_roster, new_roster, args, old_from_db, new_from_db,
                dummy_header);
 
+  // never colorize the diff output
+  colorizer colorizer(false, app.lua);
 
   if (app.opts.with_header)
     {
-      dump_header(dummy_header, old_roster, new_roster, output, false);
+      dump_header(dummy_header, old_roster, new_roster, output, colorizer, false);
     }
 
   dump_diffs(app.lua, db, old_roster, new_roster, output,
              app.opts.diff_format,
              app.opts.external_diff_args_given, app.opts.external_diff_args,
-             old_from_db, new_from_db, !app.opts.no_show_encloser);
+             old_from_db, new_from_db,
+             !app.opts.no_show_encloser,
+             colorizer);
 }
 
 
@@ -550,6 +566,7 @@ log_print_rev (app_state &      app,
                revision_t &     rev,
                string           date_fmt,
                node_restriction mask,
+               colorizer const & color,
                ostream &        out)
 {
   cert_name const author_name(author_cert_name);
@@ -563,7 +580,8 @@ log_print_rev (app_state &      app,
 
   if (app.opts.brief)
     {
-      out << rid;
+      out << color.colorize(encode_hexenc(rid.inner()(), rid.inner().made_from),
+                            colorizer::log_revision);
       log_certs(certs, out, author_name);
       if (app.opts.no_graph)
         log_certs(certs, out, date_name, date_fmt);
@@ -578,7 +596,7 @@ log_print_rev (app_state &      app,
   else
     {
       utf8 header;
-      revision_header(rid, rev, certs, date_fmt, header);
+      revision_header(rid, rev, certs, date_fmt, color, header);
 
       external header_external;
       utf8_to_system_best_effort(header, header_external);
@@ -587,7 +605,7 @@ log_print_rev (app_state &      app,
       if (!app.opts.no_files)
         {
           utf8 summary;
-          revision_summary(rev, summary);
+          revision_summary(rev, color, summary);
           external summary_external;
           utf8_to_system_best_effort(summary, summary_external);
           out << summary_external;
@@ -619,7 +637,8 @@ log_print_rev (app_state &      app,
                      app.opts.external_diff_args_given,
                      app.opts.external_diff_args,
                      true, true,
-                     !app.opts.no_show_encloser);
+                     !app.opts.no_show_encloser,
+                     color);
         }
     }
 }
@@ -838,8 +857,11 @@ log_common (app_state & app,
 
   set<revision_id> seen;
   revision_t rev;
+
+  colorizer color(app.opts.colorize && !automate, app.lua);
   // this is instantiated even when not used, but it's lightweight
-  asciik graph(output);
+  asciik graph(output, color);
+
   while(!frontier.empty() && last != 0 && next != 0)
     {
       revision_id const & rid = frontier.top().second;
@@ -935,7 +957,7 @@ log_common (app_state & app,
           else
             {
               ostringstream out;
-              log_print_rev (app, db, project, rid, rev, date_fmt, mask_diff, out);
+              log_print_rev(app, db, project, rid, rev, date_fmt, mask_diff, color, out);
 
               string out_system;
               utf8_to_system_best_effort(utf8(out.str(), origin::internal), out_system);
@@ -983,7 +1005,7 @@ CMD(log, "log", "", CMD_REF(informative)
     options::opts::brief | options::opts::diffs |
     options::opts::depth | options::opts::exclude |
     options::opts::no_merges | options::opts::no_files |
-    options::opts::no_graph)
+    options::opts::no_graph )
 {
   log_common (app, args, false, cout);
 }
============================================================
--- src/cmd_files.cc	7e9ffd4a06daeb0ef7ab7557d3a656c6b99ab2ad
+++ src/cmd_files.cc	44c13d2a7ae5e2beb2b316d24202481680779f70
@@ -14,6 +14,7 @@
 #include "annotate.hh"
 #include "revision.hh"
 #include "cmd.hh"
+#include "colorizer.hh"
 #include "diff_output.hh"
 #include "merge_content.hh"
 #include "file_io.hh"
@@ -132,7 +133,8 @@ CMD(fdiff, "fdiff", "", CMD_REF(debug), 
   make_diff(src_name, dst_name,
             src_id, dst_id,
             src.inner(), dst.inner(),
-            cout, app.opts.diff_format, pattern);
+            cout, app.opts.diff_format, 
+            pattern, colorizer(app.opts.colorize, app.lua));
 }
 
 CMD(annotate, "annotate", "", CMD_REF(informative), N_("PATH"),
============================================================
--- src/cmd_ws_commit.cc	7b91a53d8eec4e78b062092f85c945bbaaefe23a
+++ src/cmd_ws_commit.cc	e0bd38df40b3be50ffd8793c134fcc9edfb47bd9
@@ -259,7 +259,8 @@ get_log_message_interactively(lua_hooks 
   }
 
   utf8 summary;
-  revision_summary(rev, summary);
+  colorizer color(false, lua);
+  revision_summary(rev, color, summary);
 
   utf8 full_message(changelog() + cancel() + instructions() + editable() + ignored() +
                     notes() + summary(),
@@ -965,10 +966,11 @@ CMD(status, "status", "", CMD_REF(inform
 
   utf8 header;
   utf8 summary;
+  colorizer color(app.opts.colorize, app.lua);
 
   revision_header(rid, rev, author, date_t::now(), app.opts.branch, changelog,
-                  date_fmt, header);
-  revision_summary(rev, summary);
+                  date_fmt, color, header);
+  revision_summary(rev, color, summary);
 
   external header_external;
   external summary_external;
============================================================
--- src/options_list.hh	4af93dbcc45e76d732d073ced7ce21d20472cd00
+++ src/options_list.hh	e7b13e91324a35e0b05be40e49f66cc654fa2f7e
@@ -285,7 +285,8 @@ GROUPED_SIMPLE_OPTION(date_formats, no_f
                       "no-format-dates", bool,
                       gettext_noop("print date certs exactly as stored in the database"))
 
-
+GROUPED_SIMPLE_OPTION(globals, colorize, "colorize/no-colorize", bool,
+                      gettext_noop("colorize output"))                     
 OPTVAR(globals, db_type, dbname_type, )
 OPTVAR(globals, std::string, dbname_alias, )
 OPTVAR(globals, system_path, dbname, )
============================================================
--- src/asciik.cc	cf946f9a14ad309615704bc960255c50e12b636a
+++ src/asciik.cc	64600c9969226fa55cd05982364147342d3b734e
@@ -135,8 +135,8 @@ static revision_id ghost; // valid but e
 
 static revision_id ghost; // valid but empty revision_id to be used as ghost value
 
-asciik::asciik(ostream & os, size_t min_width)
-  : width(min_width), output(os)
+asciik::asciik(ostream & os, colorizer const & color, size_t min_width)
+  : width(min_width), output(os), color(color)
 {
 }
 
@@ -250,10 +250,13 @@ asciik::draw(size_t const curr_items,
 
   // prints it out
   //TODO convert line/interline/interline2 from ASCII to system charset
-  output << line << "  " << lines[0] << '\n';
-  output << interline << "  " << lines[1] << '\n';
+  output << color.colorize(line, colorizer::log_revision)
+         << "  " << lines[0] << '\n';
+  output << color.colorize(interline, colorizer::log_revision)
+         << "  " << lines[1] << '\n';
   for (int i = 2; i < num_lines; ++i)
-    output << interline2 << "  " << lines[i] << '\n';
+    output << color.colorize(interline2, colorizer::log_revision)
+           << "  " << lines[i] << '\n';
 }
 
 bool
@@ -387,7 +390,7 @@ CMD(asciik, "asciik", "", CMD_REF(debug)
   toposort(db, revs, sorted);
   reverse(sorted.begin(), sorted.end());
 
-  asciik graph(std::cout, 10);
+  asciik graph(std::cout, colorizer(app.opts.colorize, app.lua), 10);
 
   for (vector<revision_id>::const_iterator rev = sorted.begin();
        rev != sorted.end(); ++rev)
============================================================
--- src/asciik.hh	592aa966af256f50be9784bfd01c543f54d3447b
+++ src/asciik.hh	3e0dcd90804053a5e558b0ddfa6e5f3dcc462d50
@@ -11,13 +11,14 @@
 #define __ASCIIK_HH__
 
 #include <set>
+#include "colorizer.hh"
 #include "vector.hh"
 #include "vocab.hh"
 
 class asciik
 {
 public:
-  asciik(std::ostream & os, size_t min_width = 0);
+  asciik(std::ostream & os, colorizer const & color, size_t min_width = 0);
   // Prints an ASCII-k chunk using the given revisions.
   // Multiple lines are supported in annotation (the graph will stretch
   // accordingly); empty newlines at the end will be removed.
@@ -41,6 +42,7 @@ private:
   // internal state
   size_t width;
   std::ostream & output;
+  colorizer const & color;
   std::vector<revision_id> curr_row;
 };
 
============================================================
--- src/rev_output.cc	a2c70b893b31296917d1a2b974faa1da46c13f1e
+++ src/rev_output.cc	fec6728d80bca2d8d13ab49bb4ecd03310879fe5
@@ -31,40 +31,40 @@ revision_header(revision_id const rid, r
 revision_header(revision_id const rid, revision_t const & rev,
                 string const & author, date_t const date,
                 branch_name const & branch, utf8 const & changelog,
-                string const & date_fmt, utf8 & header)
+                string const & date_fmt, colorizer const & color, utf8 & header)
 {
   vector<cert> certs;
   key_id empty_key;
-  certs.push_back(cert(rid, author_cert_name, 
+  certs.push_back(cert(rid, author_cert_name,
                        cert_value(author, origin::user), empty_key));
-  certs.push_back(cert(rid, date_cert_name, 
+  certs.push_back(cert(rid, date_cert_name,
                        cert_value(date.as_iso_8601_extended(), origin::user),
                        empty_key));
-  certs.push_back(cert(rid, branch_cert_name, 
+  certs.push_back(cert(rid, branch_cert_name,
                        cert_value(branch(), origin::user), empty_key));
 
   if (!changelog().empty())
-    certs.push_back(cert(rid, changelog_cert_name, 
+    certs.push_back(cert(rid, changelog_cert_name,
                          cert_value(changelog(), origin::user), empty_key));
 
-  revision_header(rid, rev, certs, date_fmt, header);
+  revision_header(rid, rev, certs, date_fmt, color, header);
 }
 
 void
 revision_header(revision_id const rid, revision_t const & rev,
                 vector<cert> const & certs, string const & date_fmt,
-                utf8 & header)
+                colorizer const & color, utf8 & header)
 {
   ostringstream out;
 
-  out << string(70, '-') << '\n'
-      << _("Revision: ") << rid << '\n';
+  out << color.colorize(string(70, '-'), colorizer::log_revision) << '\n'
+      << color.colorize(_("Revision: "), colorizer::rev_header) << rid << '\n';
 
   for (edge_map::const_iterator i = rev.edges.begin(); i != rev.edges.end(); ++i)
     {
       revision_id parent = edge_old_revision(*i);
       if (!null_id(parent))
-        out << _("Parent:   ") << parent << '\n';
+        out << color.colorize(_("Parent:   "), colorizer::rev_header) << parent << '\n';
     }
 
   cert_name const author(author_cert_name);
@@ -76,34 +76,40 @@ revision_header(revision_id const rid, r
 
   for (vector<cert>::const_iterator i = certs.begin(); i != certs.end(); ++i)
     if (i->name == author)
-      out << _("Author:   ") << i->value << '\n';
+      out << color.colorize(_("Author:   "), colorizer::rev_header)
+          << i->value << '\n';
 
   for (vector<cert>::const_iterator i = certs.begin(); i != certs.end(); ++i)
     if (i->name == date)
       {
         if (date_fmt.empty())
-          out << _("Date:     ") << i->value << '\n';
+          out << color.colorize(_("Date:     "), colorizer::rev_header)
+              << i->value << '\n';
         else
           {
             date_t date(i->value());
-            out << _("Date:     ") << date.as_formatted_localtime(date_fmt) << '\n';
+            out << color.colorize(_("Date:     "), colorizer::rev_header)
+                << date.as_formatted_localtime(date_fmt) << '\n';
           }
       }
 
   for (vector<cert>::const_iterator i = certs.begin(); i != certs.end(); ++i)
     if (i->name == branch)
-      out << _("Branch:   ") << i->value << '\n';
+      out << color.colorize(_("Branch:   "), colorizer::rev_header)
+          << i->value << '\n';
 
   for (vector<cert>::const_iterator i = certs.begin(); i != certs.end(); ++i)
     if (i->name == tag)
-      out << _("Tag:      ") << i->value << '\n';
+      out << color.colorize(_("Tag:      "), colorizer::rev_header)
+          << i->value << '\n';
 
   out << "\n";
 
   for (vector<cert>::const_iterator i = certs.begin(); i != certs.end(); ++i)
     if (i->name == changelog)
       {
-        out << _("Changelog: ") << "\n\n" << i->value << '\n';
+        out << color.colorize(_("Changelog: "), colorizer::rev_header) << "\n\n"
+            << i->value << '\n';
         if (!i->value().empty() && i->value()[i->value().length()-1] != '\n')
           out << '\n';
       }
@@ -111,7 +117,8 @@ revision_header(revision_id const rid, r
   for (vector<cert>::const_iterator i = certs.begin(); i != certs.end(); ++i)
     if (i->name == comment)
       {
-        out << _("Comments: ") << "\n\n" << i->value << '\n';
+        out << color.colorize(_("Comments: "), colorizer::rev_header) << "\n\n"
+            << i->value << '\n';
         if (!i->value().empty() && i->value()[i->value().length()-1] != '\n')
           out << '\n';
       }
@@ -120,7 +127,7 @@ void
 }
 
 void
-revision_summary(revision_t const & rev, utf8 & summary)
+revision_summary(revision_t const & rev, colorizer const & color, utf8 & summary)
 {
   // We intentionally do not collapse the final \n into the format
   // strings here, for consistency with newline conventions used by most
@@ -138,9 +145,9 @@ revision_summary(revision_t const & rev,
       // A colon at the end of this string looked nicer, but it made
       // double-click copying from terminals annoying.
       if (null_id(parent))
-        out << _("Changes") << "\n\n";
+        out << color.colorize(_("Changes"), colorizer::rev_header) << "\n\n";
       else
-        out << _("Changes against parent ") << parent << "\n\n";
+        out << color.colorize(_("Changes against parent "), colorizer::rev_header) << parent << "\n\n";
 
       // presumably a merge rev could have an empty edge if one side won
       if (cs.empty())
@@ -148,43 +155,53 @@ revision_summary(revision_t const & rev,
 
       for (set<file_path>::const_iterator i = cs.nodes_deleted.begin();
             i != cs.nodes_deleted.end(); ++i)
-        out << (F("  dropped  %s") %*i) << '\n';
+        out << color.colorize((F("  dropped  %s") %*i).str(),
+                              colorizer::remove) << '\n';
 
       for (map<file_path, file_path>::const_iterator
             i = cs.nodes_renamed.begin();
             i != cs.nodes_renamed.end(); ++i)
-        out << (F("  renamed  %s\n"
-                  "       to  %s") % i->first % i->second) << '\n';
+        out << color.colorize((F("  renamed  %s\n"
+                                 "       to  %s") % i->first
+                                 % i->second).str(),
+                              colorizer::rename) << '\n';
 
       for (set<file_path>::const_iterator i = cs.dirs_added.begin();
             i != cs.dirs_added.end(); ++i)
-        out << (F("  added    %s") % *i) << '\n';
+        out << color.colorize((F("  added    %s") % *i).str(),
+                              colorizer::add) << '\n';
 
       for (map<file_path, file_id>::const_iterator i = cs.files_added.begin();
             i != cs.files_added.end(); ++i)
-        out << (F("  added    %s") % i->first) << '\n';
+        out << color.colorize((F("  added    %s") % i->first).str(),
+                              colorizer::add) << '\n';
 
       for (map<file_path, pair<file_id, file_id> >::const_iterator
               i = cs.deltas_applied.begin(); i != cs.deltas_applied.end(); ++i)
-        out << (F("  patched  %s") % i->first) << '\n';
+        out << color.colorize((F("  patched  %s") % i->first).str(),
+                              colorizer::change) << '\n';
 
       for (map<pair<file_path, attr_key>, attr_value >::const_iterator
              i = cs.attrs_set.begin(); i != cs.attrs_set.end(); ++i)
-        out << (F("  attr on  %s\n"
-                  "      set  %s\n"
-                  "       to  %s")
-                % i->first.first % i->first.second % i->second) << '\n';
+        out << color.colorize((F("  attr on  %s\n"
+                                 "      set  %s\n"
+                                 "       to  %s")
+                               % i->first.first % i->first.second
+                               % i->second).str(),
+                              colorizer::set) << '\n';
 
       // FIXME: naming here could not be more inconsistent
       // the cset calls it attrs_cleared
       // the command is attr drop
       // here it is called unset
-      // the revision text uses attr clear 
+      // the revision text uses attr clear
 
       for (set<pair<file_path, attr_key> >::const_iterator
              i = cs.attrs_cleared.begin(); i != cs.attrs_cleared.end(); ++i)
-        out << (F("  attr on  %s\n"
-                  "    unset  %s") % i->first % i->second) << '\n';
+        out << color.colorize((F("  attr on  %s\n"
+                                 "    unset  %s") % i->first
+                                 % i->second).str(),
+                              colorizer::unset) << '\n';
 
       out << '\n';
     }
============================================================
--- src/rev_output.hh	666dd3ed35e16d8b122b4932c2aad05a21a22e25
+++ src/rev_output.hh	5879a8268a59545c946583b65f66eda4491b979e
@@ -10,6 +10,7 @@
 #ifndef __REV_SUMMARY_HH__
 #define __REV_SUMMARY_HH__
 
+#include "colorizer.hh"
 #include "rev_types.hh"
 #include "vocab.hh"
 
@@ -17,18 +18,19 @@ void
 struct cert;
 
 void
-revision_header(revision_id const rid, revision_t const & rev, 
+revision_header(revision_id const rid, revision_t const & rev,
                 std::string const & author, date_t const date,
                 branch_name const & branch, utf8 const & changelog,
-                std::string const & date_fmt, utf8 & header);
+                std::string const & date_fmt, colorizer const & color,
+                utf8 & header);
 
 void
-revision_header(revision_id const rid, revision_t const & rev, 
+revision_header(revision_id const rid, revision_t const & rev,
                 std::vector<cert> const & certs, std::string const & date_fmt,
-                utf8 & header);
+                colorizer const & color, utf8 & header);
 
 void
-revision_summary(revision_t const & rev, utf8 & summary);
+revision_summary(revision_t const & rev, colorizer const & color, utf8 & summary);
 
 #endif  // header guard
 
============================================================
--- /dev/null	
+++ src/colorizer.cc	96964884a83e4d7d638c146821a554d033f396eb
@@ -0,0 +1,184 @@
+// Copyright (C) 2010 Thomas Keller <address@hidden>
+//
+// This program is made available under the GNU GPL version 2.0 or
+// greater. See the accompanying file COPYING for details.
+//
+// This program is distributed WITHOUT ANY WARRANTY; without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE.
+
+#include "base.hh"
+#include "colorizer.hh"
+#include "platform.hh"
+
+using std::string;
+using std::map;
+using std::make_pair;
+
+
+string colorizer::purpose_to_name(colorizer::purpose const p) const
+{
+  switch (p)
+  {
+    case normal:
+      return "normal";
+    case reset:
+      return "reset";
+
+    case add:
+      return "add";
+    case change:
+      return "change";
+    case comment:
+      return "comment";
+    case encloser:
+      return "encloser";
+    case log_revision:
+      return "log_revision";
+    case remove:
+      return "remove";
+    case rename:
+      return "rename";
+    case rev_header:
+      return "rev_header";
+    case separator:
+      return "separator";
+    case set:
+      return "set";
+    case unset:
+      return "unset";
+
+    default:
+      I(false); // should never get here
+  }
+}
+
+std::pair<colorizer::purpose, boost::tuple<string, string, string> > colorizer::map_output_color(
+  purpose const p)
+{
+  string fg, bg, style;
+  string purpose_name = purpose_to_name(p);
+
+  if (p == reset)
+    {
+      // the user doesn't need to know about reset - it's an implementation
+      // detail for us to handle
+      fg = bg = style = "";
+    }
+  else
+    {
+      lua.hook_get_output_color(purpose_name, fg, bg, style);
+    }
+
+  return std::make_pair(p, boost::make_tuple(fg_to_code(fg),
+                                             bg_to_code(bg),
+                                             style_to_code(style)));
+}
+
+string colorizer::fg_to_code(string const color) const
+{
+  if (color == "black")
+    return "\033[30m";
+  else if (color == "red")
+    return "\033[31m";
+  else if (color == "green")
+    return "\033[32m";
+  else if (color == "yellow")
+    return "\033[33m";
+  else if (color == "blue")
+    return "\033[34m";
+  else if (color == "magenta")
+    return "\033[35m";
+  else if (color == "cyan")
+    return "\033[36m";
+  else if (color == "white")
+    return "\033[37m";
+  else
+    return "\033[39m"; // default
+}
+
+string colorizer::bg_to_code(string const color) const
+{
+  if (color == "black")
+    return "\033[40m";
+  else if (color == "red")
+    return "\033[41m";
+  else if (color == "green")
+    return "\033[42m";
+  else if (color == "yellow")
+    return "\033[43m";
+  else if (color == "blue")
+    return "\033[44m";
+  else if (color == "magenta")
+    return "\033[45m";
+  else if (color == "cyan")
+    return "\033[46m";
+  else if (color == "white")
+    return "\033[47m";
+  else
+    return "\033[49m"; // default
+}
+
+string colorizer::style_to_code(string const style) const
+{
+  if (style == "none")
+    return "\033[22m\033[23m\033[24m";
+  else if (style == "bold")
+    return "\033[1m";
+  else if (style == "italic")
+    return "\033[3m";
+  else if (style == "underline")
+    return "\033[4m";
+  else
+    return "\033[22m\033[23m\033[24m"; // all off
+}
+
+colorizer::colorizer(bool enable, lua_hooks & lh) 
+  : lua(lh)
+{
+  if (!have_smart_terminal())
+    enable = false;
+
+  if (enable)
+    {
+      colormap.insert(map_output_color(normal));
+      colormap.insert(map_output_color(reset));
+
+      colormap.insert(map_output_color(add));
+      colormap.insert(map_output_color(change));
+      colormap.insert(map_output_color(comment));
+      colormap.insert(map_output_color(encloser));
+      colormap.insert(map_output_color(log_revision));
+      colormap.insert(map_output_color(remove));
+      colormap.insert(map_output_color(rename));
+      colormap.insert(map_output_color(rev_header));
+      colormap.insert(map_output_color(separator));
+      colormap.insert(map_output_color(set));
+      colormap.insert(map_output_color(unset));
+    }
+}
+
+string
+colorizer::colorize(string const & in, purpose p) const
+{
+  if (colormap.find(p) == colormap.end())
+    return in;
+
+   return get_format(p) + in + get_format(reset);
+}
+
+string
+colorizer::get_format(purpose const p) const
+{
+  boost::tuple<string, string, string> format = colormap.find(p)->second;
+
+  return format.get<0>() + format.get<1>() + format.get<2>();
+}
+
+// Local Variables:
+// mode: C++
+// fill-column: 76
+// c-file-style: "gnu"
+// indent-tabs-mode: nil
+// End:
+// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
============================================================
--- /dev/null	
+++ src/colorizer.hh	eb3d0e3dad8d447e270e0c6264bf1a4ea139013f
@@ -0,0 +1,66 @@
+// Copyright (C) 2010 Thomas Keller <address@hidden>
+//
+// This program is made available under the GNU GPL version 2.0 or
+// greater. See the accompanying file COPYING for details.
+//
+// This program is distributed WITHOUT ANY WARRANTY; without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE.
+
+#ifndef __COLORIZER_HH__
+#define __COLORIZER_HH__
+
+#include "lua_hooks.hh"
+#include "vocab.hh"
+#include <map>
+#include <boost/tuple/tuple.hpp>
+
+struct colorizer {
+
+  typedef enum { normal = 0,
+                 reset,
+
+                 add,
+                 change,
+                 comment,
+                 encloser,
+                 log_revision,
+                 remove,
+                 rename,
+                 rev_header,
+                 separator,
+                 set,
+                 unset
+                } purpose;
+
+  colorizer(bool enable, lua_hooks & lh);
+
+  std::string
+  colorize(std::string const & in, purpose p = normal) const;
+
+private:
+  std::map<purpose, boost::tuple<std::string, std::string, std::string> >
+    colormap;
+  lua_hooks & lua;
+
+  std::pair<purpose, boost::tuple<std::string, std::string, std::string> >
+  map_output_color(purpose const p);
+
+  std::string fg_to_code(std::string const color) const;
+  std::string bg_to_code(std::string const color) const;
+  std::string style_to_code(std::string const style) const;
+
+  std::string get_format(purpose const p) const;
+
+  std::string purpose_to_name(purpose const p) const;
+};
+
+#endif // __COLORIZER_HH__
+
+// Local Variables:
+// mode: C++
+// fill-column: 76
+// c-file-style: "gnu"
+// indent-tabs-mode: nil
+// End:
+// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

reply via email to

[Prev in Thread] Current Thread [Next in Thread]