# # # add_dir "tests/diff_output_formats" # # add_file "tests/diff_output_formats/__driver__.lua" # content [4df984a3a42eaa2b130b96b14d555db1d6082e17] # # add_file "tests/diff_output_formats/hello.cd" # content [eaa3e49d306b120a98b25ed7fd265948d5d9212c] # # add_file "tests/diff_output_formats/hello.cdp" # content [d9c4e4802b5e4d23f618913ae173ce8743dd6a7a] # # add_file "tests/diff_output_formats/hello.dst" # content [94ebfe438b30bf18631c1846b2891b818f46aa23] # # add_file "tests/diff_output_formats/hello.src" # content [80ad86578e12a12c838cd4ff7ca226aa6bcc44e9] # # add_file "tests/diff_output_formats/hello.ud" # content [f94d586a2a50808c101ea613684f539c9a83b158] # # add_file "tests/diff_output_formats/hello.udp" # content [854cacf9498bc94f755f041ea3fff85844873c01] # # patch "app_state.cc" # from [39ee6b031773ef782d71f16687987918c7d1a41e] # to [bba166138c53ad01ea4888021cfb5ade353895d5] # # patch "app_state.hh" # from [a6cba0558a4c377dda413b557c10781cd33668c1] # to [5a1af9fcaa2c9a42a03debe7916812f77ab4670b] # # patch "cmd_diff_log.cc" # from [5b89fe46b77f87f89cb871fb96170dd8a5352fd4] # to [caa41fb44a1e29cf9e989c82afbdf29126d9c951] # # patch "cmd_files.cc" # from [808056118d77c0d6ac4350e7a2fb5cd54dce81d7] # to [442a350ece280378c85d8c4d7ce01ad9af98ae47] # # patch "diff_patch.cc" # from [1b235d6bac2c940f6790276db02b84dd5827b82e] # to [46b072dce126c4f2447fcfb46f7162aae34fe05a] # # patch "diff_patch.hh" # from [fb4f8c2825228e0131db5166e5b656fc96a958c4] # to [a963d4afdaa1db93a25924ec994f114e413f69c2] # # patch "lua_hooks.cc" # from [a56ee45b9fc403fbe263a447c56ac9ef9bb28896] # to [5cd3dc9801c357f9127c51fd454332d3d13196bf] # # patch "lua_hooks.hh" # from [d456540bc98234e1ddc0d9533405f69bf3a10fe0] # to [6a5820f9ff8d94090bf2d12f5e406b2e32747699] # # patch "monotone.cc" # from [f4e2ff9591c12d439494daab38a74693715ab463] # to [33c26757b7cfd298eef5dc29aef789eb649405e0] # # patch "monotone.texi" # from [f05dadb3d39c670a9f726fca0995a1872a20fca9] # to [5f8de984d6503c737e2fd31a8b790b904403f582] # # patch "options.hh" # from [95d68d8fde1bc778d716e7ec3cffd6d2e924109f] # to [57cf0542e5083345452323550fb416cb0d28c7ca] # # patch "std_hooks.lua" # from [cae9b1a8f02d17b2b356167cc3cf2b12abcc88c1] # to [8a7070484c06a9fcc5db1d2288862e99c956f3ae] # # patch "tests/test_hooks.lua" # from [b4b22761bc0ad96cc8af40b8c7a74f2afa0ee2f3] # to [bd224e643fb22b048f55924561045f6d9bab70c4] # # patch "testsuite.lua" # from [a958a32341dacf6077633d520376af16b54fb735] # to [5eef9df65c55d57447fb154990c6a165339d8d3b] # ============================================================ --- tests/diff_output_formats/__driver__.lua 4df984a3a42eaa2b130b96b14d555db1d6082e17 +++ tests/diff_output_formats/__driver__.lua 4df984a3a42eaa2b130b96b14d555db1d6082e17 @@ -0,0 +1,33 @@ +function test_one(base) + local src = base .. ".src" + local dst = base .. ".dst" + local ud = base .. ".ud" + local cd = base .. ".cd" + local udp = base .. ".udp" + local cdp = base .. ".cdp" + if not get(src) or not get(dst) then + error("case '" .. base .. "': mandatory component missing", 2) + return + end + check(mtn("fload"), 0, nil, nil, {src}) + check(mtn("fload"), 0, nil, nil, {dst}) + src = sha1(src) + dst = sha1(dst) + + if get(ud) then + check(mtn("fdiff", base, base, src, dst), 0, {ud}, nil, nil) + end + if get(cd) then + check(mtn("fdiff", "-c", base, base, src, dst), 0, {cd}, nil, nil) + end + if get(udp) then + check(mtn("fdiff", "-p", base, base, src, dst), 0, {udp}, nil, nil) + end + if get(cdp) then + check(mtn("fdiff", "-c", "-p", base, base, src, dst), 0, {cdp}, nil, nil) + end +end + +mtn_setup() + +test_one("hello") ============================================================ --- tests/diff_output_formats/hello.cd eaa3e49d306b120a98b25ed7fd265948d5d9212c +++ tests/diff_output_formats/hello.cd eaa3e49d306b120a98b25ed7fd265948d5d9212c @@ -0,0 +1,14 @@ +*** hello 80ad86578e12a12c838cd4ff7ca226aa6bcc44e9 +--- hello 94ebfe438b30bf18631c1846b2891b818f46aa23 +*************** +*** 9,11 **** +--- 9,17 ---- + { + say_hello(); + } ++ ++ void say_goodbye() ++ { ++ printf("goodbye\n"); ++ } ++ ============================================================ --- tests/diff_output_formats/hello.cdp d9c4e4802b5e4d23f618913ae173ce8743dd6a7a +++ tests/diff_output_formats/hello.cdp d9c4e4802b5e4d23f618913ae173ce8743dd6a7a @@ -0,0 +1,14 @@ +*** hello 80ad86578e12a12c838cd4ff7ca226aa6bcc44e9 +--- hello 94ebfe438b30bf18631c1846b2891b818f46aa23 +*************** int main() +*** 9,11 **** +--- 9,17 ---- + { + say_hello(); + } ++ ++ void say_goodbye() ++ { ++ printf("goodbye\n"); ++ } ++ ============================================================ --- tests/diff_output_formats/hello.dst 94ebfe438b30bf18631c1846b2891b818f46aa23 +++ tests/diff_output_formats/hello.dst 94ebfe438b30bf18631c1846b2891b818f46aa23 @@ -0,0 +1,17 @@ +#include "hello.h" + +void say_hello() +{ + printf("hello, world\n"); +} + +int main() +{ + say_hello(); +} + +void say_goodbye() +{ + printf("goodbye\n"); +} + ============================================================ --- tests/diff_output_formats/hello.src 80ad86578e12a12c838cd4ff7ca226aa6bcc44e9 +++ tests/diff_output_formats/hello.src 80ad86578e12a12c838cd4ff7ca226aa6bcc44e9 @@ -0,0 +1,11 @@ +#include "hello.h" + +void say_hello() +{ + printf("hello, world\n"); +} + +int main() +{ + say_hello(); +} ============================================================ --- tests/diff_output_formats/hello.ud f94d586a2a50808c101ea613684f539c9a83b158 +++ tests/diff_output_formats/hello.ud f94d586a2a50808c101ea613684f539c9a83b158 @@ -0,0 +1,12 @@ +--- hello 80ad86578e12a12c838cd4ff7ca226aa6bcc44e9 ++++ hello 94ebfe438b30bf18631c1846b2891b818f46aa23 +@@ -9,3 +9,9 @@ + { + say_hello(); + } ++ ++void say_goodbye() ++{ ++ printf("goodbye\n"); ++} ++ ============================================================ --- tests/diff_output_formats/hello.udp 854cacf9498bc94f755f041ea3fff85844873c01 +++ tests/diff_output_formats/hello.udp 854cacf9498bc94f755f041ea3fff85844873c01 @@ -0,0 +1,12 @@ +--- hello 80ad86578e12a12c838cd4ff7ca226aa6bcc44e9 ++++ hello 94ebfe438b30bf18631c1846b2891b818f46aa23 +@@ -9,3 +9,9 @@ int main() + { + say_hello(); + } ++ ++void say_goodbye() ++{ ++ printf("goodbye\n"); ++} ++ ============================================================ --- app_state.cc 39ee6b031773ef782d71f16687987918c7d1a41e +++ app_state.cc bba166138c53ad01ea4888021cfb5ade353895d5 @@ -49,6 +49,7 @@ search_root(current_root_path()), depth(-1), last(-1), next(-1), diff_format(unified_diff), diff_args_provided(false), + diff_show_encloser(false), execute(false), bind_address(""), bind_port(""), bind_stdio(false), use_transport_auth(true), missing(false), unknown(false), ============================================================ --- app_state.hh a6cba0558a4c377dda413b557c10781cd33668c1 +++ app_state.hh 5a1af9fcaa2c9a42a03debe7916812f77ab4670b @@ -72,6 +72,7 @@ system_path pidfile; diff_type diff_format; bool diff_args_provided; + bool diff_show_encloser; utf8 diff_args; bool execute; utf8 bind_address; ============================================================ --- cmd_diff_log.cc 5b89fe46b77f87f89cb871fb96170dd8a5352fd4 +++ cmd_diff_log.cc caa41fb44a1e29cf9e989c82afbdf29126d9c951 @@ -220,7 +220,6 @@ dump_diffs(cset const & cs, app_state & app, bool new_is_archived, - diff_type type, set const & paths, bool limit_paths = false) { @@ -330,7 +329,7 @@ delta_entry_src(i), delta_entry_dst(i), old_lines, new_lines, - cout, type); + cout, app); } } } @@ -338,11 +337,10 @@ static void dump_diffs(cset const & cs, app_state & app, - bool new_is_archived, - diff_type type) + bool new_is_archived) { set dummy; - dump_diffs(cs, app, new_is_archived, type, dummy); + dump_diffs(cs, app, new_is_archived, dummy); } CMD(diff, N_("informative"), N_("[PATH]..."), @@ -353,10 +351,9 @@ "is used by default."), OPT_REVISION % OPT_DEPTH % OPT_EXCLUDE % OPT_UNIFIED_DIFF % OPT_CONTEXT_DIFF % OPT_EXTERNAL_DIFF % - OPT_EXTERNAL_DIFF_ARGS) + OPT_EXTERNAL_DIFF_ARGS % OPT_SHOW_ENCLOSER) { bool new_is_archived; - diff_type type = app.diff_format; ostringstream header; temp_node_id_source nis; @@ -495,10 +492,10 @@ } cout << "# " << "\n"; - if (type == external_diff) { + if (app.diff_format == external_diff) { do_external_diff(included, app, new_is_archived); } else - dump_diffs(included, app, new_is_archived, type); + dump_diffs(included, app, new_is_archived); } static void @@ -747,8 +744,8 @@ for (edge_map::const_iterator e = rev.edges.begin(); e != rev.edges.end(); ++e) { - dump_diffs(edge_changes(e), app, true, unified_diff, - diff_paths, !mask.empty()); + dump_diffs(edge_changes(e), app, true, diff_paths, + !mask.empty()); } } ============================================================ --- cmd_files.cc 808056118d77c0d6ac4350e7a2fb5cd54dce81d7 +++ cmd_files.cc 442a350ece280378c85d8c4d7ce01ad9af98ae47 @@ -22,7 +22,7 @@ using std::string; using std::vector; -// fload and fmerge are simple commands for debugging the line +// fload, fmerge, and fdiff are simple commands for debugging the line // merger. CMD(fload, N_("debug"), "", N_("load file contents into db"), OPT_NONE) @@ -75,6 +75,43 @@ } +CMD(fdiff, N_("debug"), N_("[-c, -u, -p] "), + N_("diff 2 files and output result"), + OPT_CONTEXT_DIFF % OPT_UNIFIED_DIFF % OPT_SHOW_ENCLOSER) +{ + if (args.size() != 4) + throw usage(name); + + string const + & src_name = idx(args, 0)(), + & dst_name = idx(args, 1)(); + + file_id + src_id(idx(args, 2)()), + dst_id(idx(args, 3)()); + + file_data src, dst; + + N(app.db.file_version_exists (src_id), + F("source file id does not exist")); + + N(app.db.file_version_exists (dst_id), + F("destination file id does not exist")); + + app.db.get_file_version(src_id, src); + app.db.get_file_version(dst_id, dst); + + vector src_lines, dst_lines; + + split_into_lines(src.inner()(), src_lines); + split_into_lines(dst.inner()(), dst_lines); + + make_diff(src_name, dst_name, + src_id, dst_id, + src_lines, dst_lines, + cout, app); +} + CMD(annotate, N_("informative"), N_("PATH"), N_("print annotated copy of the file from REVISION"), OPT_REVISION % OPT_BRIEF) ============================================================ --- diff_patch.cc 1b235d6bac2c940f6790276db02b84dd5827b82e +++ diff_patch.cc 46b072dce126c4f2447fcfb46f7162aae34fe05a @@ -765,13 +765,70 @@ struct hunk_consumer { + vector const & a; + vector const & b; + size_t ctx; + ostream & ost; + string const & encloser_pattern; + app_state & app; + size_t a_begin, b_begin, a_len, b_len; + long skew; + ssize_t encloser_last_search; + ssize_t encloser_last_match; + 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; virtual void delete_at(size_t a_pos) = 0; + virtual void find_encloser(size_t pos, string & encloser); virtual ~hunk_consumer() {} + hunk_consumer(vector const & a, + vector const & b, + size_t ctx, + ostream & ost, + string const & encloser_pattern, + app_state & app) + : a(a), b(b), ctx(ctx), ost(ost), encloser_pattern(encloser_pattern), + app(app), a_begin(0), b_begin(0), a_len(0), b_len(0), skew(0), + encloser_last_search(0), encloser_last_match(0) {} }; +/* Find, and write to ENCLOSER, the nearest line before POS which matches + ENCLOSER_PATTERN. We remember the last line scanned, and the matched, to + avoid duplication of effort. */ + +void +hunk_consumer::find_encloser(size_t pos, string & encloser) +{ + if (encloser_pattern == "") + return; + + // We need the ability for i and last to go negative so that we do not + // have an infinite loop when last==0 (i.e. the first time 'round). + ssize_t last = encloser_last_search; + encloser_last_search = pos; + for (ssize_t i = min(pos, a.size()-1); i >= last; i--) + if (app.lua.patternmatch (a[i], encloser_pattern)) + { + L(FL("find_encloser: from %u matching line %d, \"%s\"") + % pos % i % a[i]); + encloser_last_match = i; + // the number 40 is chosen to match GNU diff. it could safely be + // increased up to about 60 without overflowing the standard + // terminal width. + encloser = string(" ") + a[i].substr(0, 40); + return; + } + + if (encloser_last_match) + { + ssize_t i = encloser_last_match; + L(FL("find_encloser: from %u matching cached %d, \"%s\"") + % pos % i % a[i]); + encloser = string(" ") + a[i].substr(0, 40); + } +} + void walk_hunk_consumer(vector const & lcs, vector const & lines1, vector const & lines2, @@ -822,17 +879,13 @@ struct unidiff_hunk_writer : public hunk_consumer { - vector const & a; - vector const & b; - size_t ctx; - ostream & ost; - size_t a_begin, b_begin, a_len, b_len; - long skew; vector hunk; unidiff_hunk_writer(vector const & a, vector const & b, size_t ctx, - ostream & ost); + ostream & ost, + string const & encloser_pattern, + app_state & app); virtual void flush_hunk(size_t pos); virtual void advance_to(size_t newpos); virtual void insert_at(size_t b_pos); @@ -843,10 +896,10 @@ unidiff_hunk_writer::unidiff_hunk_writer(vector const & a, vector const & b, size_t ctx, - ostream & ost) -: a(a), b(b), ctx(ctx), ost(ost), - a_begin(0), b_begin(0), - a_len(0), b_len(0), skew(0) + ostream & ost, + string const & encloser_pattern, + app_state & app) + : hunk_consumer(a, b, ctx, ost, encloser_pattern, app) {} void unidiff_hunk_writer::insert_at(size_t b_pos) @@ -892,7 +945,9 @@ if (b_len > 1) ost << "," << b_len; } - ost << " @@" << endl; + string encloser; + find_encloser(a_begin + ctx, encloser); + ost << " @@" << encloser << endl; copy(hunk.begin(), hunk.end(), ostream_iterator(ost, "\n")); } @@ -942,12 +997,6 @@ struct cxtdiff_hunk_writer : public hunk_consumer { - vector const & a; - vector const & b; - size_t ctx; - ostream & ost; - size_t a_begin, b_begin, a_len, b_len; - long skew; vector inserts; vector deletes; vector from_file; @@ -957,7 +1006,9 @@ cxtdiff_hunk_writer(vector const & a, vector const & b, size_t ctx, - ostream & ost); + ostream & ost, + string const & encloser_pattern, + app_state & app); virtual void flush_hunk(size_t pos); virtual void advance_to(size_t newpos); virtual void insert_at(size_t b_pos); @@ -969,11 +1020,11 @@ cxtdiff_hunk_writer::cxtdiff_hunk_writer(vector const & a, vector const & b, size_t ctx, - ostream & ost) - : a(a), b(b), ctx(ctx), ost(ost), - a_begin(0), b_begin(0), - a_len(0), b_len(0), skew(0), - have_insertions(false), have_deletions(false) + ostream & ost, + string const & encloser_pattern, + app_state & app) + : hunk_consumer(a, b, ctx, ost, encloser_pattern, app), + have_insertions(false), have_deletions(false) {} void cxtdiff_hunk_writer::insert_at(size_t b_pos) @@ -1009,8 +1060,11 @@ b_len++; } - ost << "***************" << endl; + string encloser; + find_encloser(a_begin + ctx, encloser); + ost << "***************" << encloser << endl; + ost << "*** " << (a_begin + 1) << "," << (a_begin + a_len) << " ****" << endl; if (have_deletions) copy(from_file.begin(), from_file.end(), ostream_iterator(ost, "\n")); @@ -1114,7 +1168,7 @@ vector const & lines1, vector const & lines2, ostream & ost, - diff_type type) + app_state & app) { vector left_interned; vector right_interned; @@ -1138,14 +1192,18 @@ min(lines1.size(), lines2.size()), back_inserter(lcs)); - switch (type) + string pattern; + if (app.diff_show_encloser) + app.lua.hook_get_encloser_pattern (filename1, pattern); + + switch (app.diff_format) { case unified_diff: { ost << "--- " << filename1 << "\t" << id1 << endl; ost << "+++ " << filename2 << "\t" << id2 << endl; - unidiff_hunk_writer hunks(lines1, lines2, 3, ost); + unidiff_hunk_writer hunks(lines1, lines2, 3, ost, pattern, app); walk_hunk_consumer(lcs, left_interned, right_interned, hunks); break; } @@ -1154,7 +1212,7 @@ ost << "*** " << filename1 << "\t" << id1 << endl; ost << "--- " << filename2 << "\t" << id2 << endl; - cxtdiff_hunk_writer hunks(lines1, lines2, 3, ost); + cxtdiff_hunk_writer hunks(lines1, lines2, 3, ost, pattern, app); walk_hunk_consumer(lcs, left_interned, right_interned, hunks); break; } @@ -1204,65 +1262,6 @@ } } -// regression blockers go here -static void unidiff_append_test() -{ - string src(string("#include \"hello.h\"\n") - + "\n" - + "void say_hello()\n" - + "{\n" - + " printf(\"hello, world\\n\");\n" - + "}\n" - + "\n" - + "int main()\n" - + "{\n" - + " say_hello();\n" - + "}\n"); - - string dst(string("#include \"hello.h\"\n") - + "\n" - + "void say_hello()\n" - + "{\n" - + " printf(\"hello, world\\n\");\n" - + "}\n" - + "\n" - + "int main()\n" - + "{\n" - + " say_hello();\n" - + "}\n" - + "\n" - + "void say_goodbye()\n" - + "{\n" - + " printf(\"goodbye\\n\");\n" - + "}\n" - + "\n"); - - string ud(string("--- hello.c\t0123456789abcdef0123456789abcdef01234567\n") - + "+++ hello.c\tabcdef0123456789abcdef0123456789abcdef01\n" - + "@@ -9,3 +9,9 @@\n" - + " {\n" - + " say_hello();\n" - + " }\n" - + "+\n" - + "+void say_goodbye()\n" - + "+{\n" - + "+ printf(\"goodbye\\n\");\n" - + "+}\n" - + "+\n"); - - vector src_lines, dst_lines; - split_into_lines(src, src_lines); - split_into_lines(dst, dst_lines); - stringstream sst; - make_diff("hello.c", "hello.c", - file_id(id("0123456789abcdef0123456789abcdef01234567")), - file_id(id("abcdef0123456789abcdef0123456789abcdef01")), - src_lines, dst_lines, sst, unified_diff); - cout << sst.str() << endl; - BOOST_CHECK(sst.str() == ud); -} - - // high tech randomizing test static void randomizing_merge_test() @@ -1406,7 +1405,6 @@ void add_diff_patch_tests(test_suite * suite) { I(suite); - suite->add(BOOST_TEST_CASE(&unidiff_append_test)); suite->add(BOOST_TEST_CASE(&merge_prepend_test)); suite->add(BOOST_TEST_CASE(&merge_append_test)); suite->add(BOOST_TEST_CASE(&merge_additions_test)); ============================================================ --- diff_patch.hh fb4f8c2825228e0131db5166e5b656fc96a958c4 +++ diff_patch.hh a963d4afdaa1db93a25924ec994f114e413f69c2 @@ -35,7 +35,7 @@ std::vector const & lines1, std::vector const & lines2, std::ostream & ost, - diff_type type); + app_state & app); bool merge3(std::vector const & ancestor, std::vector const & left, ============================================================ --- lua_hooks.cc a56ee45b9fc403fbe263a447c56ac9ef9bb28896 +++ lua_hooks.cc 5cd3dc9801c357f9127c51fd454332d3d13196bf @@ -206,7 +206,26 @@ } } +// utility function, not really a hook +bool +lua_hooks::patternmatch(const string & haystack, const string & needle) +{ + int matchstart; + // If string.find returned nil, extract_int will fail, so the ok() return + // is the result we want. + return Lua(st) + .push_str("string") + .get_tab() + .push_str("find") + .get_fn(-2) + .push_str(haystack) + .push_str(needle) + .call(2,1) + .extract_int(matchstart) + .ok(); +} + // concrete hooks // nb: if you're hooking lua to return your passphrase, you don't care if we @@ -483,6 +502,25 @@ } bool +lua_hooks::hook_get_encloser_pattern(std::string const & path, + std::string & pattern) +{ + bool exec_ok + = Lua(st) + .func("get_encloser_pattern") + .push_str(path) + .call(1, 1) + .extract_str(pattern) + .ok(); + + // If the hook fails, make sure pattern is set to something sane + // (the empty string, which will disable enclosers for this file). + if (!exec_ok) + pattern = ""; + return exec_ok; +} + +bool lua_hooks::hook_use_inodeprints() { bool use = false, exec_ok = false; ============================================================ --- lua_hooks.hh d456540bc98234e1ddc0d9533405f69bf3a10fe0 +++ lua_hooks.hh 6a5820f9ff8d94090bf2d12f5e406b2e32747699 @@ -41,6 +41,10 @@ void load_rcfile(utf8 const & file); void load_rcfile(any_path const & file, bool required); + // not a hook, but a convenience wrapper around Lua's str.find(). + // currently only returns yes/no. + bool patternmatch(std::string const & haystack, std::string const & needle); + // cert hooks bool hook_expand_selector(std::string const & sel, std::string & exp); bool hook_expand_date(std::string const & sel, std::string & exp); @@ -97,6 +101,9 @@ std::string const & oldrev, std::string const & newrev); + bool hook_get_encloser_pattern(std::string const & path, + std::string & pattern); + // workspace hooks bool hook_use_inodeprints(); ============================================================ --- monotone.cc f4e2ff9591c12d439494daab38a74693715ab463 +++ monotone.cc 33c26757b7cfd298eef5dc29aef789eb649405e0 @@ -77,10 +77,12 @@ {"no-merges", 0, POPT_ARG_NONE, NULL, OPT_NO_MERGES, gettext_noop("exclude merges when printing logs"), NULL}, {"set-default", 0, POPT_ARG_NONE, NULL, OPT_SET_DEFAULT, gettext_noop("use the current arguments as the future default"), NULL}, {"exclude", 0, POPT_ARG_STRING, &argstr, OPT_EXCLUDE, gettext_noop("leave out anything described by its argument"), NULL}, - {"unified", 0, POPT_ARG_NONE, NULL, OPT_UNIFIED_DIFF, gettext_noop("use unified diff format"), NULL}, - {"context", 0, POPT_ARG_NONE, NULL, OPT_CONTEXT_DIFF, gettext_noop("use context diff format"), NULL}, + {"unified", 'u', POPT_ARG_NONE, NULL, OPT_UNIFIED_DIFF, gettext_noop("use unified diff format"), NULL}, + {"context", 'c', POPT_ARG_NONE, NULL, OPT_CONTEXT_DIFF, gettext_noop("use context diff format"), NULL}, {"external", 0, POPT_ARG_NONE, NULL, OPT_EXTERNAL_DIFF, gettext_noop("use external diff hook for generating diffs"), NULL}, {"diff-args", 0, POPT_ARG_STRING, &argstr, OPT_EXTERNAL_DIFF_ARGS, gettext_noop("argument to pass external diff hook"), NULL}, + {"show-encloser", 'p', POPT_ARG_NONE, NULL, OPT_SHOW_ENCLOSER, gettext_noop("show the function containing each block of changes"), NULL}, + {"show-c-function", 0, POPT_ARG_NONE, NULL, OPT_SHOW_ENCLOSER, gettext_noop("another name for --show-encloser (for compatibility with GNU diff)"), NULL}, {"execute", 'e', POPT_ARG_NONE, NULL, OPT_EXECUTE, gettext_noop("perform the associated file operation"), NULL}, {"bind", 0, POPT_ARG_STRING, &argstr, OPT_BIND, gettext_noop("address:port to listen on (default :4691)"), NULL}, {"missing", 0, POPT_ARG_NONE, NULL, OPT_MISSING, gettext_noop("perform the operations for files missing from workspace"), NULL}, @@ -115,7 +117,7 @@ {"key", 'k', POPT_ARG_STRING, &argstr, OPT_KEY_NAME, gettext_noop("set key for signatures"), NULL}, {"db", 'd', POPT_ARG_STRING, &argstr, OPT_DB_NAME, gettext_noop("set name of database"), NULL}, {"root", 0, POPT_ARG_STRING, &argstr, OPT_ROOT, gettext_noop("limit search for workspace to specified root"), NULL}, - {"verbose", 0, POPT_ARG_NONE, NULL, OPT_VERBOSE, gettext_noop("verbose completion output"), NULL}, + {"verbose", 'v', POPT_ARG_NONE, NULL, OPT_VERBOSE, gettext_noop("verbose completion output"), NULL}, {"keydir", 0, POPT_ARG_STRING, &argstr, OPT_KEY_DIR, gettext_noop("set location of key store"), NULL}, {"confdir", 0, POPT_ARG_STRING, &argstr, OPT_CONF_DIR, gettext_noop("set location of configuration directory"), NULL}, { NULL, 0, 0, NULL, 0, NULL, NULL } @@ -504,6 +506,10 @@ app.set_diff_args(utf8(string(argstr))); break; + case OPT_SHOW_ENCLOSER: + app.diff_show_encloser = true; + break; + case OPT_EXECUTE: app.execute = true; break; ============================================================ --- monotone.texi f05dadb3d39c670a9f726fca0995a1872a20fca9 +++ monotone.texi 5f8de984d6503c737e2fd31a8b790b904403f582 @@ -87,8 +87,9 @@ * Index:: Index of concepts and functions @end menu - address@hidden Complete table of contents address@hidden ifnottex @contents @@ -4594,9 +4595,9 @@ @end group @end smallexample address@hidden mtn diff address@hidden mtn diff --context address@hidden mtn diff --external address@hidden address@hidden mtn diff [--unified] [--show-encloser] address@hidden mtn diff --context [--show-encloser] address@hidden mtn diff --external address@hidden @itemx mtn diff @var{pathname...} @itemx mtn diff address@hidden @itemx mtn diff address@hidden @var{pathname...} @@ -4631,9 +4632,30 @@ to files changed within the current subdirectory of the workspace. The output format of @command{diff} is controlled by the options address@hidden, @option{--context}, and @option{--external}. address@hidden, @option{--context}, @option{--show-encloser}, and address@hidden By default, monotone uses its built-in diff +algorithm to produce a listing in ``unified diff'' format (analogous +to running the program @code{diff -u}); you can also explicitly +request this with @option{--unified}. The built-in diff algorithm can +also produce ``context diff'' format (analogous to @code{diff -c}), +which you request by specifying @option{--context}. The short options +that @command{diff} accepts for these modes, @option{-u} and address@hidden, also work. + +In either of these modes, you can request that monotone print the name +of the top-level code construct that encloses each ``hunk'' of +changes, with @option{--show-encloser}. The options that address@hidden accepts for this mode, @option{-p} and address@hidden, also work. Monotone finds the enclosing +construct by scanning backward from the first changed line in each +hunk for a line that matches a regular expression. The default +regular expression is correct for many programming languages. You can +adjust the expression used with the Lua hook address@hidden; @ref{Hooks}. + + @option{--unified} requests the ``unified diff'' format, the default -(analogous to running the program @code{diff -u}). @option{--context} +. @option{--context} request the ``context diff'' format (analogous to running the program @code{diff -c}). Both of these formats are generated directly by monotone, using its built-in diff algorithm. @@ -7484,15 +7506,26 @@ @subsection External Diff Tools Differences between files can be shown in a number of ways, varying -according to user preference and file type. This hook allows +according to user preference and file type. These hooks allow customisation of the way file differences are shown. @ftable @code address@hidden get_encloser_pattern (@var{file_path}) + +Called for each file when @command{diff} is given the address@hidden option (and @emph{not} the address@hidden option). @var{file_path} is the pathname of the +file that is being diffed. The hook should return a string constant +containing a Lua-format regular expression; this regular expression +will be used to find lines that, in that file, name the ``top-level'' +constructs enclosing each ``hunk'' of changes. The default is address@hidden, which is correct for many programming languages. + @item external_diff (@var{file_path}, @var{old_data}, @var{new_data}, @var{is_binary}, @var{diff_args}, @var{old_rev}, @var{new_rev}) Called for each file when @command{diff} is given the address@hidden command. @var{file_path} is the pathname of the address@hidden option. @var{file_path} is the pathname of the file that is being diffed. @var{old_data} and @var{new_data} are the data contents of the old and the new file. If the data is binary, @var{is_binary} will be true, otherwise false. @var{old_rev} and ============================================================ --- options.hh 95d68d8fde1bc778d716e7ec3cffd6d2e924109f +++ options.hh 57cf0542e5083345452323550fb416cb0d28c7ca @@ -62,6 +62,7 @@ #define OPT_REALLYQUIET 48 #define OPT_STDIO 49 #define OPT_NO_TRANSPORT_AUTH 50 +#define OPT_SHOW_ENCLOSER 51 // Local Variables: // mode: C++ ============================================================ --- std_hooks.lua cae9b1a8f02d17b2b356167cc3cf2b12abcc88c1 +++ std_hooks.lua 8a7070484c06a9fcc5db1d2288862e99c956f3ae @@ -182,6 +182,14 @@ return guess_binary_file_contents(name) end +-- given a file name, return a regular expression which is suitable +-- for scanning backward from a diff hunk for the name of the enclosing +-- top-level construct. +function get_encloser_pattern(name) + -- this default is correct surprisingly often + return "^[%w$_]" +end + function edit_comment(basetext, user_log_message) local exe = nil if (program_exists_in_path("vi")) then exe = "vi" end ============================================================ --- tests/test_hooks.lua b4b22761bc0ad96cc8af40b8c7a74f2afa0ee2f3 +++ tests/test_hooks.lua bd224e643fb22b048f55924561045f6d9bab70c4 @@ -131,3 +131,7 @@ return true end end + +function get_encloser_pattern(name) + return "^[%w$_]" +end ============================================================ --- testsuite.lua a958a32341dacf6077633d520376af16b54fb735 +++ testsuite.lua 5eef9df65c55d57447fb154990c6a165339d8d3b @@ -644,3 +644,4 @@ table.insert(tests, "netsync_over_pipes") table.insert(tests, "ls_unknown_of_unknown_subdir") table.insert(tests, "automate_branches") +table.insert(tests, "diff_output_formats")