# # # add_file "ignore_set.cc" # content [1ba21228e02f1cce879e7733f0d2ff28f22f5c66] # # add_file "ignore_set.hh" # content [e097a3a75b97560175a18087a58c6e0a8a62b533] # # patch "Makefile.am" # from [eb7cf82ecaf2da8091ede85fe5fe98dd5401928a] # to [c9c16101bf4da822e651fe5d0cc9cbb3d0bc8eee] # # patch "pcrewrap.cc" # from [b83084a2540fa0356128435d1dbbd56466bc4bc2] # to [8a8f9cfeee0a8e6efe25f633f8125515a3da8232] # # patch "pcrewrap.hh" # from [007bcae3d6a968650f662857cf008102d5249944] # to [4aa00973c48774d1e97022978a2348507e6888d5] # # patch "std_hooks.lua" # from [a53348ca15431cc67266c65e6215640f6f55eee7] # to [5da58188a67c8aeeee00da512ec542564f2421e6] # # patch "tests/syntax_errors_in_.mtn-ignore/__driver__.lua" # from [370e177545af48aa97d064961f5dd6610ba825f7] # to [c2736f40724c8d06178e59fe40c0628cedb90abf] # # patch "tests/syntax_errors_in_.mtn-ignore/stderr-ref" # from [6d982783b4455a358e195a462a8347cee1b41e83] # to [b03851ac775426b380de16b797ffe7f7e719bb92] # # patch "work.cc" # from [bb21460bb9c30ff19c93e378cdf5706cca93e30e] # to [1e1b1da2ed6bb0563ac8b064d579ef34c9dbb3a2] # # patch "work.hh" # from [def9c5b6c7776dfa16c2bc9c0b83bc493ef56b04] # to [450169c76ad6d95d581f437ff7e02905c7751e4a] # ============================================================ --- ignore_set.cc 1ba21228e02f1cce879e7733f0d2ff28f22f5c66 +++ ignore_set.cc 1ba21228e02f1cce879e7733f0d2ff28f22f5c66 @@ -0,0 +1,439 @@ +// Copyright (C) 2007 Zack Weinberg +// +// 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 "ignore_set.hh" +#include "file_io.hh" +#include "pcrewrap.hh" +#include "vector.hh" +#include +#include "safe_map.hh" +#include "simplestring_xform.hh" + +using std::vector; +using std::string; +using std::map; + +// wrapper logic + +typedef vector re_set; + +struct ignore_set_impl +{ + re_set in; + re_set out; + + ignore_set_impl(); + bool included(file_path const &); +}; + +ignore_set::~ignore_set() +{ + if (imp) + delete imp; +} + +bool +ignore_set::included(file_path const & path) +{ + if (!imp) + imp = new ignore_set_impl(); + return imp->included(path); +} + + +// the syntax of .mtn-ignore is as follows: +// +// any trailing '\n' and/or '\r' is stripped from each input line, as are +// all leading and trailing ' ' and/or '\t' characters. after this is done: +// +// empty lines are ignored. +// lines beginning with '#' are comments, and are ignored. +// lines beginning with '!' contribute to the not-ignore list rather than +// the ignore list. +// +// after # and ! processing, we look for leading or trailing slashes. +// they are replaced by constructs that cause a leading / to +// match at any directory boundary including the root, and a trailing / to +// match both the contents of a directory and the directory itself. + +static void +parse_one_ignore_line(string const & orig_line, + map & in_pats, + map & out_pats, + char const *filename, int lineno) +{ + typedef string::size_type st; + + st beg = orig_line.find_first_not_of(" \t"); + st end = orig_line.find_last_not_of(" \t\r\n"); + + if (beg == string::npos || end == string::npos) + return; // line is empty or entirely whitespace + if (orig_line[beg] == '#') + return; // comment + + string line(orig_line, beg, end - beg + 1); + I(!line.empty()); + + bool this_one_in = true; + if (line[0] == '!') // don't-ignore regex + { + line.erase(0,1); + this_one_in = false; + if (line.empty()) + { + W(F("on line %d of %s: empty don't-ignore regex") + % lineno % filename); + return; + } + } + + if (line[0] == '/') + { + if (line.size() == 1) + { + W(F("on line %d of %s: lone \"/\" can't match anything") + % lineno % filename); + return; + } + line.replace(0, 1, "(?:/|^)"); + } + + if (line[line.size()-1] == '/') + line.replace(line.size() - 1, 1, "(?:/|$)"); + + try + { + pcre::regex pat(line); + + map::iterator x = in_pats.find(line); + if (this_one_in) + { + if (x != in_pats.end()) + { + W(F("on line %d of %s: duplicate regex \"%s\"") + % lineno % filename % orig_line); + return; + } + safe_insert(in_pats, make_pair(line, pat)); + } + else + { + if (x != in_pats.end()) + in_pats.erase(x); + else + { + map::iterator y = out_pats.find(line); + if (y != out_pats.end()) + { + W(F("on line %d of %s: duplicate regex \"%s\"") + % lineno % filename % orig_line); + return; + } + safe_insert(out_pats, make_pair(line, pat)); + } + } + } + catch (informative_failure & e) + { + W(F("on line %d of %s: %s") + % lineno % filename % e.what()); + return; + } +} + +// this array comprises the default set of filename patterns to be ignored +// if unknown. it is an array rather than a pre-optimized regular +// expression for three reasons: first, it's easier to edit that way; +// second, as an optimization, if prepare_ignore_regexps sees a not-ignore +// pattern that exactly matches one of these, it will drop it from the set +// of to-ignore patterns rather than add to the not-ignore set; and third, +// there is a command that prints out this list as if it were an ignore +// file. (this last is why there are comments embedded in the array.) +// +// note that this array is run through the same parser as .mtn-ignore; this +// is necessary to handle comments and directory patterns. note also that +// we explicitly escape all non-metacharacter punctuation, as a precaution. + +static char const * const default_ignores[] = { + "# c/c++", + "\\.a$", + "\\.so$", + "\\.o$", + "\\.la$", + "\\.lo$", + "/core$", + "/core\\.\\d+$", + "# java", + "\\.class$", + "# python", + "\\.pyc$", + "\\.pyo$", + "# gettext", + "\\.g?mo$", + "# intltool", + "\\.intltool\\-merge\\-cache$", + "# TeX", + "\\.aux$", + "# backup files", + "\\.bak$", + "\\.orig$", + "\\.rej$", + "\\~$", + "# vim creates .foo.swp files", + "\\.[^/]*\\.swp$", + "# emacs creates #foo# files", + "/\\#[^/]*\\#$", + "# other VCSes (where metadata is stored in named files):", + "\\.scc$", + "# desktop/directory configuration metadata", + "/\\.DS_Store$", + "/desktop\\.ini$", + "# autotools detritus", + "/autom4te\\.cache/", + "/\\.deps/", + "/\\.libs/", + "# Cons/SCons detritus", + "/\\.consign/", + "/\\.sconsign/", + "# other VCSes (where metadata is stored in named dirs):", + "/CVS/", + "/\\.svn/", + "/SCCS/", + "/_darcs/", + "/\\.cdv/", + "/\\.git/", + "/\\.bzr/", + "/\\.hg/", + 0 +}; + +ignore_set_impl::ignore_set_impl() +{ + map in_pats; + map out_pats; + + // read defaults + // parse_one_ignore_line should never give a diagnostic for these, so + // we don't worry about translating the fake file tag we use + for (size_t i = 0; default_ignores[i]; i++) + parse_one_ignore_line(default_ignores[i], in_pats, out_pats, + "", i+1); + + // read .mtn-ignore if it exists + file_path ignorefile = file_path_internal(".mtn-ignore"); + if (file_exists(ignorefile)) + { + data ignorefile_dat; + vector ignorefile_vec; + size_t lineno = 1; + + read_data(ignorefile, ignorefile_dat); + split_into_lines(ignorefile_dat(), ignorefile_vec); + for (vector::const_iterator i = ignorefile_vec.begin(); + i != ignorefile_vec.end(); i++, lineno++) + parse_one_ignore_line(*i, in_pats, out_pats, ".mtn-ignore", lineno); + } + + // now copy the compiled regexes into permanent storage + in.reserve(in_pats.size()); + out.reserve(out_pats.size()); + for (map::const_iterator i = in_pats.begin(); + i != in_pats.end(); i++) + in.push_back(i->second); + + for (map::const_iterator i = out_pats.begin(); + i != out_pats.end(); i++) + out.push_back(i->second); +} + +bool +ignore_set_impl::included(file_path const & path) +{ + for (re_set::const_iterator i = in.begin(); i != in.end(); i++) + if (i->match(path.as_internal())) + goto maybe_matched; + + return false; + + maybe_matched: + for (re_set::const_iterator i = out.begin(); i != out.end(); i++) + if (i->match(path.as_internal())) + return false; + + return true; +} + + +#ifdef BUILD_UNIT_TESTS +#include "unit_tests.hh" +#include + +#define PARSE_ONE(T, I, O) \ + parse_one_ignore_line(T, I, O, BOOST_CURRENT_FUNCTION, __LINE__) +#define PARSE_ONE_LINENO(T, I, O, L) \ + parse_one_ignore_line(T, I, O, BOOST_CURRENT_FUNCTION, L) + + +UNIT_TEST(ignore, line_parsing) +{ + struct single_line_case + { + char const * str; + char const * exp_in; + char const * exp_out; + }; + single_line_case const tcases[] = { + // commentary + { "", 0, 0 }, + { "address@hidden&*()_+", 0, 0 }, + { "#! /bin /sh", 0, 0 }, + { " \t\n\r", 0, 0 }, + { " \r\t\n", 0, 0 }, + { " #fnord", 0, 0 }, + + // things which are not comments but are still ignored + { "/", 0, 0 }, + { "!", 0, 0 }, + { "!/", 0, 0 }, + + // whitespace stripping + { "abc", "abc", 0 }, + { " abc", "abc", 0 }, + { "abc ", "abc", 0 }, + { " abc \t", "abc", 0 }, + { "abc\r\n", "abc", 0 }, + { "\rabc", "\rabc", 0 }, // impossible in real usage but. + + // interior whitespace is preserved + { "a b c d e f", "a b c d e f", 0 }, + + // leading punctuation + { "\\.foo", "\\.foo", 0 }, + { "^foo", "^foo", 0 }, + { "[ab]cd", "[ab]cd", 0 }, + { "(foo|bar|baz)\\.o", "(foo|bar|baz)\\.o", 0 }, + + // hiding leading metas + { "[ ]foo", "[ ]foo", 0 }, + { "[#]foo", "[#]foo", 0 }, + { "[!]foo", "[!]foo", 0 }, + + // directory slashes + { "/foo", "(?:/|^)foo", 0 }, + { "[/]foo", "[/]foo", 0 }, + { "foo/", "foo(?:/|$)", 0 }, + { "foo[/]", "foo[/]", 0 }, + { "/foo/", "(?:/|^)foo(?:/|$)", 0 }, + + // negation + { "!abc", 0, "abc" }, + { " !abc ", 0, "abc" }, + { "! abc ", 0, " abc" }, + + // # is not a comment character after ! + { "!#abc", 0, "#abc" }, + { "![#]abc", 0, "[#]abc" }, + + // / is magic after ! + { "!/foo", 0, "(?:/|^)foo" }, + { "![/]foo", 0, "[/]foo" }, + + { 0, 0, 0 } + }; + + map in, out; + for (size_t i = 0; tcases[i].str; i++) + { + UNIT_TEST_CHECKPOINT((FL("#%d: '%s'") % i % tcases[i].str).str().c_str()); + in.clear(); + out.clear(); + PARSE_ONE_LINENO(tcases[i].str, in, out, i); + + if (tcases[i].exp_in) + { + UNIT_TEST_CHECK(out.size() == 0); + UNIT_TEST_CHECK(in.size() == 1); + UNIT_TEST_CHECK_MSG(in.begin()->first == tcases[i].exp_in, + FL("in[0]=='%s' == '%s'") % in.begin()->first + % tcases[i].exp_in); + } + else if (tcases[i].exp_out) + { + UNIT_TEST_CHECK(in.size() == 0); + UNIT_TEST_CHECK(out.size() == 1); + UNIT_TEST_CHECK_MSG(out.begin()->first == tcases[i].exp_out, + FL("out[0]=='%s' == '%s'") % out.begin()->first + % tcases[i].exp_out); + } + else + { + UNIT_TEST_CHECK(in.size() == 0); + UNIT_TEST_CHECK(out.size() == 0); + } + } +} + +UNIT_TEST(ignore, line_interactions) +{ + map in, out; + + UNIT_TEST_CHECKPOINT("two included"); + PARSE_ONE("foo", in, out); + PARSE_ONE("bar", in, out); + UNIT_TEST_CHECK(in.size() == 2); + UNIT_TEST_CHECK(out.size() == 0); + + in.clear(); + out.clear(); + UNIT_TEST_CHECKPOINT("two excluded"); + PARSE_ONE("!foo", in, out); + PARSE_ONE("!bar", in, out); + UNIT_TEST_CHECK(in.size() == 0); + UNIT_TEST_CHECK(out.size() == 2); + + in.clear(); + out.clear(); + UNIT_TEST_CHECKPOINT("duplicate included"); + PARSE_ONE("foo", in, out); + PARSE_ONE("foo", in, out); + UNIT_TEST_CHECK(in.size() == 1); + UNIT_TEST_CHECK(out.size() == 0); + + in.clear(); + out.clear(); + UNIT_TEST_CHECKPOINT("duplicate excluded"); + PARSE_ONE("!foo", in, out); + PARSE_ONE("!foo", in, out); + UNIT_TEST_CHECK(in.size() == 0); + UNIT_TEST_CHECK(out.size() == 1); + + in.clear(); + out.clear(); + UNIT_TEST_CHECKPOINT("remove an include"); + PARSE_ONE("foo", in, out); + PARSE_ONE("bar", in, out); + PARSE_ONE("!foo", in, out); + PARSE_ONE("!quux", in, out); + UNIT_TEST_CHECK(in.size() == 1); + UNIT_TEST_CHECK(out.size() == 1); + UNIT_TEST_CHECK(in.begin()->first == string("bar")); + UNIT_TEST_CHECK(out.begin()->first == string("quux")); +} + +#endif + +// 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: ============================================================ --- ignore_set.hh e097a3a75b97560175a18087a58c6e0a8a62b533 +++ ignore_set.hh e097a3a75b97560175a18087a58c6e0a8a62b533 @@ -0,0 +1,38 @@ +// Copyright (C) 2007 Zack Weinberg +// +// 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 IGNORE_SET_HH +#define IGNORE_SET_HH + +struct ignore_set_impl; +struct file_path; + +struct ignore_set +{ + ignore_set_impl * imp; + + ignore_set() : imp(0) {} + ~ignore_set(); + + bool included(file_path const & path); + +private: + ignore_set(ignore_set const &); + ignore_set & operator=(ignore_set const &); +}; + +#endif + +// 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: ============================================================ --- Makefile.am eb7cf82ecaf2da8091ede85fe5fe98dd5401928a +++ Makefile.am c9c16101bf4da822e651fe5d0cc9cbb3d0bc8eee @@ -31,6 +31,7 @@ MOST_SOURCES = \ transforms.cc transforms.hh \ update.cc update.hh \ work.cc work_migration.cc work.hh \ + ignore_set.cc ignore_set.hh \ cert.cc cert.hh \ project.cc project.hh \ outdated_indicator.cc outdated_indicator.hh \ @@ -302,12 +303,12 @@ UNIT_TEST_SOURCES = \ # these files contain unit tests UNIT_TEST_SOURCES = \ basic_io.cc charset.cc commands.cc crypto_tests.cc cset.cc \ - dates.cc diff_patch.cc globish.cc graph.cc keys.cc netcmd.cc \ - netxx_pipe.cc numeric_vocab.cc option.cc outdated_indicator.cc \ - packet.cc paths.cc refiner.cc restrictions.cc rev_height.cc \ - revision.cc roster.cc roster_merge.cc simplestring_xform.cc \ - string_queue.cc transforms.cc unit_tests.cc uri.cc vocab.cc \ - xdelta.cc + dates.cc diff_patch.cc ignore_set.cc globish.cc graph.cc \ + keys.cc netcmd.cc netxx_pipe.cc numeric_vocab.cc option.cc \ + outdated_indicator.cc packet.cc paths.cc refiner.cc \ + restrictions.cc rev_height.cc revision.cc roster.cc \ + roster_merge.cc simplestring_xform.cc string_queue.cc \ + transforms.cc unit_tests.cc uri.cc vocab.cc xdelta.cc # these files do not contain unit tests, but are required for unit testing # and must be recompiled for that purpose ============================================================ --- pcrewrap.cc b83084a2540fa0356128435d1dbbd56466bc4bc2 +++ pcrewrap.cc 8a8f9cfeee0a8e6efe25f633f8125515a3da8232 @@ -51,17 +51,6 @@ flags_to_internal(pcre::flags f) return i; } -inline unsigned int -get_capturecount(void const * bd) -{ - unsigned int cc; - int err = pcre_fullinfo(static_cast(bd), 0, - PCRE_INFO_CAPTURECOUNT, - static_cast(&cc)); - I(err == 0); - return cc; -} - namespace pcre { void regex::init(char const * pattern, flags options) @@ -69,14 +58,17 @@ namespace pcre int errcode; int erroff; char const * err; - basedat = pcre_compile2(pattern, flags_to_internal(options), - &errcode, &err, &erroff, 0); - if (!basedat) + pcre_t * bd = pcre_compile2(pattern, flags_to_internal(options), + &errcode, &err, &erroff, 0); + if (!bd) pcre_compile_error(errcode, err, erroff, pattern); - pcre_extra *ed = pcre_study(basedat, 0, &err); + pcre_extra *ed = pcre_study(bd, 0, &err); if (err) - pcre_study_error(err, pattern); + { + pcre_free(bd); + pcre_study_error(err, pattern); + } if (!ed) { // I resent that C++ requires this cast. @@ -84,14 +76,16 @@ namespace pcre std::memset(ed, 0, sizeof(pcre_extra)); } - // We set a fairly low recursion depth to avoid stack overflow. + // We set a fairly low recursion limit to avoid stack overflow. // Per pcrestack(3), one should assume 500 bytes per recursion; // it should be safe to let pcre have a megabyte of stack, so // that's a depth of 2000, give or take. (For reference, the // default stack limit on Linux is 8MB.) ed->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION; ed->match_limit_recursion = 2000; - extradat = ed; + + basedat = boost::shared_ptr(bd, pcre_free); + extradat = boost::shared_ptr(ed, pcre_free); } regex::regex(char const * pattern, flags options) @@ -104,14 +98,6 @@ namespace pcre this->init(pattern.c_str(), options); } - regex::~regex() - { - if (basedat) - pcre_free(const_cast(basedat)); - if (extradat) - pcre_free(const_cast(extradat)); - } - bool regex::match(string const & subject, string::const_iterator startptr, @@ -121,7 +107,7 @@ namespace pcre if (startptr != string::const_iterator(0)) startoffset = &*startptr - &*subject.data(); - int rc = pcre_exec(basedat, extradat, + int rc = pcre_exec(basedat.get(), extradat.get(), subject.data(), subject.size(), startoffset, flags_to_internal(options), 0, 0); if (rc == 0) ============================================================ --- pcrewrap.hh 007bcae3d6a968650f662857cf008102d5249944 +++ pcrewrap.hh 4aa00973c48774d1e97022978a2348507e6888d5 @@ -18,6 +18,8 @@ struct pcre_extra; struct real_pcre; struct pcre_extra; +#include + namespace pcre { enum flags @@ -50,16 +52,9 @@ namespace pcre struct regex { private: - // disable the default and copy constructors - we never need to copy - // these, and this lets us use bare pointers below instead of - // boost::shared_ptr. - regex(); - regex(regex const &); - regex & operator=(regex const &); - // data - struct real_pcre const * basedat; - struct pcre_extra const * extradat; + boost::shared_ptr basedat; + boost::shared_ptr extradat; // used by constructors void init(char const *, pcre::flags); @@ -67,7 +62,6 @@ namespace pcre public: regex(char const * pattern, pcre::flags options = DEFAULT); regex(std::string const & pattern, pcre::flags options = DEFAULT); - ~regex(); bool match(std::string const & subject, std::string::const_iterator startoffset ============================================================ --- std_hooks.lua a53348ca15431cc67266c65e6215640f6f55eee7 +++ std_hooks.lua 5da58188a67c8aeeee00da512ec542564f2421e6 @@ -102,94 +102,6 @@ end return false end -function ignore_file(name) - -- project specific - if (ignored_files == nil) then - ignored_files = {} - local ignfile = io.open(".mtn-ignore", "r") - if (ignfile ~= nil) then - local line = ignfile:read() - while (line ~= nil) do - if line ~= "" then - table.insert(ignored_files, line) - end - line = ignfile:read() - end - io.close(ignfile) - end - end - - local warn_reported_file = false - for i, line in pairs(ignored_files) - do - if (line ~= nil) then - local pcallstatus, result = pcall(function() - return regex.search(line, name) - end) - if pcallstatus == true then - -- no error from the regex.search call - if result == true then return true end - else - -- regex.search had a problem, warn the user their - -- .mtn-ignore file syntax is wrong - if not warn_reported_file then - io.stderr:write("mtn: warning: while matching file '" - .. name .. "':\n") - warn_reported_file = true - end - io.stderr:write(".mtn-ignore:" .. i .. ": warning: " .. result - .. "\n\t- skipping this regex for " - .. "all remaining files.\n") - ignored_files[i] = nil - end - end - end - - local file_pats = { - -- c/c++ - "%.a$", "%.so$", "%.o$", "%.la$", "%.lo$", "^core$", - "/core$", "/core%.%d+$", - -- java - "%.class$", - -- python - "%.pyc$", "%.pyo$", - -- gettext - "%.g?mo$", - -- intltool - "%.intltool%-merge%-cache$", - -- TeX - "%.aux$", - -- backup files - "%.bak$", "%.orig$", "%.rej$", "%~$", - -- vim creates .foo.swp files - "%.[^/]*%.swp$", - -- emacs creates #foo# files - "%#[^/]*%#$", - -- other VCSes (where metadata is stored in named files): - "%.scc$", - -- desktop/directory configuration metadata - "^%.DS_Store$", "/%.DS_Store$", "^desktop%.ini$", "/desktop%.ini$" - } - - local dir_pats = { - -- autotools detritus: - "autom4te%.cache", "%.deps", "%.libs", - -- Cons/SCons detritus: - "%.consign", "%.sconsign", - -- other VCSes (where metadata is stored in named dirs): - "CVS", "%.svn", "SCCS", "_darcs", "%.cdv", "%.git", "%.bzr", "%.hg" - } - - for _, pat in ipairs(file_pats) do - if string.find(name, pat) then return true end - end - for _, pat in ipairs(dir_pats) do - if dir_matches(name, pat) then return true end - end - - return false; -end - -- return true means "binary", false means "text", -- nil means "unknown, try to guess" function binary_file(name) ============================================================ --- tests/syntax_errors_in_.mtn-ignore/__driver__.lua 370e177545af48aa97d064961f5dd6610ba825f7 +++ tests/syntax_errors_in_.mtn-ignore/__driver__.lua c2736f40724c8d06178e59fe40c0628cedb90abf @@ -9,9 +9,8 @@ check(raw_mtn("ls", "unknown"), 0, true, check(get("mtn-ignore", ".mtn-ignore")) check(raw_mtn("ls", "unknown"), 0, true, true) + check(get("stdout-ref")) check(get("stderr-ref")) - check(samefile("stdout", "stdout-ref")) +check(samefile("stderr", "stderr-ref")) --- the first line of stderr may vary from run to run -check(tailfile("stderr", 1) == readfile("stderr-ref")) ============================================================ --- tests/syntax_errors_in_.mtn-ignore/stderr-ref 6d982783b4455a358e195a462a8347cee1b41e83 +++ tests/syntax_errors_in_.mtn-ignore/stderr-ref b03851ac775426b380de16b797ffe7f7e719bb92 @@ -1,62 +1,31 @@ -.mtn-ignore:1: warning: error near char 2 of regex "\": \ at end of pattern - - skipping this regex for all remaining files. -.mtn-ignore:2: warning: error near char 3 of regex "\c": \c at end of pattern - - skipping this regex for all remaining files. -.mtn-ignore:3: warning: error near char 6 of regex "x{3,1}": numbers out of order in {} quantifier - - skipping this regex for all remaining files. -.mtn-ignore:4: warning: error near char 8 of regex "x{99999}": number too big in {} quantifier - - skipping this regex for all remaining files. -.mtn-ignore:5: warning: error near char 5 of regex "[abc": missing terminating ] for character class - - skipping this regex for all remaining files. -.mtn-ignore:6: warning: error near char 4 of regex "[z-a]": range out of order in character class - - skipping this regex for all remaining files. -.mtn-ignore:7: warning: error near char 1 of regex "*": nothing to repeat - - skipping this regex for all remaining files. -.mtn-ignore:8: warning: error near char 3 of regex "(?h)": unrecognized character after (? - - skipping this regex for all remaining files. -.mtn-ignore:9: warning: error near char 1 of regex "[:alpha:]": POSIX named classes are supported only within a class - - skipping this regex for all remaining files. -.mtn-ignore:10: warning: error near char 5 of regex "(abc": missing ) - - skipping this regex for all remaining files. -.mtn-ignore:11: warning: error near char 6 of regex "abc\3": reference to non-existent subpattern - - skipping this regex for all remaining files. -.mtn-ignore:12: warning: error near char 7 of regex "(?#abc": missing ) after comment - - skipping this regex for all remaining files. -.mtn-ignore:13: warning: error near char 1 of regex ")": unmatched parentheses - - skipping this regex for all remaining files. -.mtn-ignore:14: warning: error near char 5 of regex "(? 255 - - skipping this regex for all remaining files. -.mtn-ignore:26: warning: error near char 5 of regex "(?C1": closing ) for (?C expected - - skipping this regex for all remaining files. -.mtn-ignore:27: warning: error near char 4 of regex "(?R)": recursive call could loop indefinitely - - skipping this regex for all remaining files. -.mtn-ignore:28: warning: error near char 4 of regex "(?P*)": unrecognized character after (?P - - skipping this regex for all remaining files. -.mtn-ignore:29: warning: error near char 5 of regex "(?P<*>x)": syntax error in subpattern name (missing terminator) - - skipping this regex for all remaining files. -.mtn-ignore:30: warning: error near char 38 of regex "(?P)": subpattern name is too long (maximum 32 characters) - - skipping this regex for all remaining files. -.mtn-ignore:31: warning: error near char 4 of regex "\777": octal value is greater than \377 (not in UTF-8 mode) - - skipping this regex for all remaining files. +mtn: warning: on line 1 of .mtn-ignore: error near char 2 of regex "\": \ at end of pattern +mtn: warning: on line 2 of .mtn-ignore: error near char 3 of regex "\c": \c at end of pattern +mtn: warning: on line 3 of .mtn-ignore: error near char 6 of regex "x{3,1}": numbers out of order in {} quantifier +mtn: warning: on line 4 of .mtn-ignore: error near char 8 of regex "x{99999}": number too big in {} quantifier +mtn: warning: on line 5 of .mtn-ignore: error near char 5 of regex "[abc": missing terminating ] for character class +mtn: warning: on line 6 of .mtn-ignore: error near char 4 of regex "[z-a]": range out of order in character class +mtn: warning: on line 7 of .mtn-ignore: error near char 1 of regex "*": nothing to repeat +mtn: warning: on line 8 of .mtn-ignore: error near char 3 of regex "(?h)": unrecognized character after (? +mtn: warning: on line 9 of .mtn-ignore: error near char 1 of regex "[:alpha:]": POSIX named classes are supported only within a class +mtn: warning: on line 10 of .mtn-ignore: error near char 5 of regex "(abc": missing ) +mtn: warning: on line 11 of .mtn-ignore: error near char 6 of regex "abc\3": reference to non-existent subpattern +mtn: warning: on line 12 of .mtn-ignore: error near char 7 of regex "(?#abc": missing ) after comment +mtn: warning: on line 13 of .mtn-ignore: error near char 1 of regex ")": unmatched parentheses +mtn: warning: on line 14 of .mtn-ignore: error near char 5 of regex "(? 255 +mtn: warning: on line 26 of .mtn-ignore: error near char 5 of regex "(?C1": closing ) for (?C expected +mtn: warning: on line 27 of .mtn-ignore: error near char 4 of regex "(?R)": recursive call could loop indefinitely +mtn: warning: on line 28 of .mtn-ignore: error near char 4 of regex "(?P*)": unrecognized character after (?P +mtn: warning: on line 29 of .mtn-ignore: error near char 5 of regex "(?P<*>x)": syntax error in subpattern name (missing terminator) +mtn: warning: on line 30 of .mtn-ignore: error near char 38 of regex "(?P)": subpattern name is too long (maximum 32 characters) +mtn: warning: on line 31 of .mtn-ignore: error near char 4 of regex "\777": octal value is greater than \377 (not in UTF-8 mode) ============================================================ --- work.cc bb21460bb9c30ff19c93e378cdf5706cca93e30e +++ work.cc 1e1b1da2ed6bb0563ac8b064d579ef34c9dbb3a2 @@ -30,6 +30,7 @@ #include "ui.hh" #include "charset.hh" #include "lua_hooks.hh" +#include "pcrewrap.hh" using std::deque; using std::exception; @@ -490,14 +491,19 @@ workspace::ignore_file(file_path const & bool workspace::ignore_file(file_path const & path) { - if (!know_ignore_hook) + if (suppress_ignores) + return false; + + // check for and run the old hook, for backward compatibility + if (look_for_lua_ignore_hook) { - have_ignore_hook = lua->obsolete_hook_ignore_file_defined(); - know_ignore_hook = true; + have_lua_ignore_hook = lua->obsolete_hook_ignore_file_defined(); + look_for_lua_ignore_hook = false; } - if (have_ignore_hook) + if (have_lua_ignore_hook) return lua->obsolete_hook_ignore_file(path); - return false; + + return ignores.included(path); } void ============================================================ --- work.hh def9c5b6c7776dfa16c2bc9c0b83bc493ef56b04 +++ work.hh 450169c76ad6d95d581f437ff7e02905c7751e4a @@ -17,6 +17,7 @@ #include "paths.hh" #include "roster.hh" #include "database.hh" +#include "ignore_set.hh" // // this file defines structures to deal with the "workspace" of a tree @@ -206,19 +207,26 @@ struct workspace // lua hooks, we don't have to know about app_state. they are pointers // for the sake of the unit-test constructor below. workspace(database & db, lua_hooks & lua) - : db(&db), lua(&lua), have_ignore_hook(false), know_ignore_hook(false) - {}; + : db(&db), lua(&lua), ignores(), suppress_ignores(false), + look_for_lua_ignore_hook(true), have_lua_ignore_hook(false) + {} -#ifdef BUILD_UNIT_TESTS // this is for restrictions.cc - workspace() : db(0), lua(0), have_ignore_hook(false), know_ignore_hook(true) - {}; + // this is for use from unit tests (e.g. restrictions.cc); it disables + // ignores altogether, and many methods will crash if called. +#ifdef BUILD_UNIT_TESTS + workspace() + : db(0), lua(0), ignores(), suppress_ignores(true), + look_for_lua_ignore_hook(false), have_lua_ignore_hook(false) + {} #endif - + private: database * db; lua_hooks * lua; - bool have_ignore_hook; - bool know_ignore_hook; + ignore_set ignores; + bool suppress_ignores; + bool look_for_lua_ignore_hook; + bool have_lua_ignore_hook; }; // Local Variables: