[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Monotone-commits-diffs] net.venge.monotone.colored-diff: 90a61d2166a6c
From: |
code |
Subject: |
[Monotone-commits-diffs] net.venge.monotone.colored-diff: 90a61d2166a6c30e950a0d02f590b83ca9b6b39d |
Date: |
Sun, 10 Apr 2011 11:22:44 +0200 (CEST) |
revision: 90a61d2166a6c30e950a0d02f590b83ca9b6b39d
date: 2011-04-09T23:38:14
author: Richard Hopkins <address@hidden>
branch: net.venge.monotone.colored-diff
changelog:
propagate from branch 'net.venge.monotone' (head
239d3d013399793455ccc3f42246163591e35631)
to branch 'net.venge.monotone.colored-diff' (head
bf94eb3b34add43865cfe4dc8c5c59934efa48da)
manifest:
format_version "1"
new_manifest [c015cbb5dbe8d45415528b6e146a5b474d6a1953]
old_revision [239d3d013399793455ccc3f42246163591e35631]
add_file "src/colorizer.cc"
content [96964884a83e4d7d638c146821a554d033f396eb]
add_file "src/colorizer.hh"
content [eb3d0e3dad8d447e270e0c6264bf1a4ea139013f]
patch "Makefile.am"
from [d8bebdb36192f09f35b5737b9975fce92b11eeb9]
to [45e7b8adf59d3c5227c340937bd27a20267e3aeb]
patch "NEWS"
from [710b0adc68789d90faf583d85b094f93295ac19e]
to [fbd21978307072241795228e418ee6dc2dd19db7]
patch "doc/monotone.texi"
from [0e40017dfe8ba652e6af4585a2ac176c8bd12dcc]
to [73c733d069b4ff2fbd203a396c7cadd7aed8f78b]
patch "src/asciik.cc"
from [cf946f9a14ad309615704bc960255c50e12b636a]
to [64600c9969226fa55cd05982364147342d3b734e]
patch "src/asciik.hh"
from [592aa966af256f50be9784bfd01c543f54d3447b]
to [3e0dcd90804053a5e558b0ddfa6e5f3dcc462d50]
patch "src/cmd_diff_log.cc"
from [b24dffb0470d057fa1e91ccc5a81627b40252e78]
to [66d959146c432421ca24988535a9b250aa860a36]
patch "src/cmd_files.cc"
from [f15378efd25e597762776ad6e9c6f78c1a4ff191]
to [3bb32dddf14bd79e630069837220aeb2e67d9346]
patch "src/cmd_ws_commit.cc"
from [d9dbacb820c6d070c4952ee2b0f143e61e85631e]
to [b22c4c8871fe38e55da487318326755c699d4873]
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 [0462e302b89179f4acb28ecb91f4255140d4a4a7]
to [9bb72269e07a2b32e6c5db912731564cf9bbe62e]
patch "src/rev_output.cc"
from [a2c70b893b31296917d1a2b974faa1da46c13f1e]
to [fec6728d80bca2d8d13ab49bb4ecd03310879fe5]
patch "src/rev_output.hh"
from [666dd3ed35e16d8b122b4932c2aad05a21a22e25]
to [5879a8268a59545c946583b65f66eda4491b979e]
patch "src/std_hooks.lua"
from [ada658bd275399032411e0f520d9900bad966dc4]
to [c8cd84c99c2b34b1ed32a0c5b58c9ab415f1be48]
old_revision [bf94eb3b34add43865cfe4dc8c5c59934efa48da]
patch "test/func/db_opt_fallback_mechanisms/__driver__.lua"
from [09354a970b921effa2850aa4ad722ce95a433ea8]
to [0b0da944f19f7a0be42165069fdc1bb8862fd6ee]
patch "test/func/serve-automate/__driver__.lua"
from [d41f819fd49060c45bb154dde614699b0cb938e7]
to [ae4b03baf16d8401f31a32c8377021f879788d01]
============================================================
--- Makefile.am d8bebdb36192f09f35b5737b9975fce92b11eeb9
+++ Makefile.am 45e7b8adf59d3c5227c340937bd27a20267e3aeb
@@ -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 710b0adc68789d90faf583d85b094f93295ac19e
+++ NEWS fbd21978307072241795228e418ee6dc2dd19db7
@@ -400,6 +400,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 0e40017dfe8ba652e6af4585a2ac176c8bd12dcc
+++ doc/monotone.texi 73c733d069b4ff2fbd203a396c7cadd7aed8f78b
@@ -5878,7 +5878,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...}
@@ -5920,7 +5920,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
@@ -5942,6 +5942,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,
@@ -6169,7 +6173,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
@@ -6240,8 +6244,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 ada658bd275399032411e0f520d9900bad966dc4
+++ src/std_hooks.lua c8cd84c99c2b34b1ed32a0c5b58c9ab415f1be48
@@ -1540,3 +1540,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 b24dffb0470d057fa1e91ccc5a81627b40252e78
+++ src/cmd_diff_log.cc 66d959146c432421ca24988535a9b250aa860a36
@@ -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)
@@ -436,9 +445,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,
@@ -446,7 +457,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);
}
@@ -475,16 +487,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);
}
@@ -549,6 +565,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);
@@ -562,7 +579,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);
@@ -577,7 +595,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);
@@ -586,7 +604,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;
@@ -618,7 +636,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);
}
}
}
@@ -841,8 +860,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;
@@ -938,7 +960,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);
@@ -986,7 +1008,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 f15378efd25e597762776ad6e9c6f78c1a4ff191
+++ src/cmd_files.cc 3bb32dddf14bd79e630069837220aeb2e67d9346
@@ -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 d9dbacb820c6d070c4952ee2b0f143e61e85631e
+++ src/cmd_ws_commit.cc b22c4c8871fe38e55da487318326755c699d4873
@@ -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 0462e302b89179f4acb28ecb91f4255140d4a4a7
+++ src/options_list.hh 9bb72269e07a2b32e6c5db912731564cf9bbe62e
@@ -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:
============================================================
--- test/func/serve-automate/__driver__.lua d41f819fd49060c45bb154dde614699b0cb938e7
+++ test/func/serve-automate/__driver__.lua ae4b03baf16d8401f31a32c8377021f879788d01
@@ -15,7 +15,7 @@ check(
local errors = run_remote_stdio(server, "l17:interface_versione", 1, 0, "e")
check(
table.maxn(errors) == 1 and
- errors[1] == "misuse: Sorry, you aren't allowed to do that."
+ errors[1] == "misuse: sorry, you aren't allowed to do that."
)
server:stop()
============================================================
--- test/func/db_opt_fallback_mechanisms/__driver__.lua 09354a970b921effa2850aa4ad722ce95a433ea8
+++ test/func/db_opt_fallback_mechanisms/__driver__.lua 0b0da944f19f7a0be42165069fdc1bb8862fd6ee
@@ -14,4 +14,4 @@ check(raw_mtn("au", "remote", "interface
-- and some commands should use :memory: as default because they
-- just need a temporary throw-away database to work properly
check(raw_mtn("au", "remote", "interface_version", "--remote-stdio-host", "http://code.monotone.ca/monotone", "--key="), 0, false, true)
-check(qgrep("No database given; assuming ':memory:' database", "stderr"))
+check(qgrep("no database given; assuming ':memory:' database", "stderr"))
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Monotone-commits-diffs] net.venge.monotone.colored-diff: 90a61d2166a6c30e950a0d02f590b83ca9b6b39d,
code <=