# # add_file "tests/t_inventory.at" # # patch "ChangeLog" # from [023b33156c29e14765acde2bde4ae86da95ec975] # to [94575c8742c6dfcc814110dd908bcb2286588aef] # # patch "app_state.cc" # from [7a6eec6df5336f398ef9a587f8ffd102b7beba2e] # to [6ae71dd8eab819023b30c62cc0cf22701dc20d71] # # patch "app_state.hh" # from [27b58d6683bc026b41c07308738952067bf82926] # to [34ce0c063205bb5c5bee6cf8e7016f1d7bd5d056] # # patch "basic_io.cc" # from [362c354a98656867a4874ce83f15faf4c63dcaf5] # to [ba25d4a49aa0e24421049e530556797881134051] # # patch "basic_io.hh" # from [4f6304d3d7afc8979fcf702573c76080b8ac3002] # to [baf5570f0a8bf91af447396c49cee0acda484c5b] # # patch "commands.cc" # from [6374417a8d763aec13e45054cf672798f542db2a] # to [0c4901c64287831a4c93077ed1af4c7bf87e0176] # # patch "manifest.cc" # from [63b95a89a40d5d8c24fda2e2103b31e53dc67026] # to [70fa82f906f4029e043dd3260176dd58b00d3396] # # patch "manifest.hh" # from [2665667c3cf960e64f39301fea45d1e4e67e5dba] # to [8e814036b2b84d20634979a941f2cb27412eff48] # # patch "monotone.1" # from [33e5e256e415dd185535ac1331d44a9de3f243cc] # to [90747fd6770890c29786a41b48dbc807e13a5d9b] # # patch "monotone.cc" # from [7b86d611ebb4f89b6fb733b5e322bd500302d020] # to [12be19ffe02763642e688d13c17266c4f02db05f] # # patch "monotone.texi" # from [282642c04b943449c20985b705d672098605f94d] # to [25cae95b3310e747b4da85e6ce864ecad0a28b0d] # # patch "tests/t_inventory.at" # from [] # to [bd593ada60d57c005007137cccec56d172955290] # # patch "tests/t_status_missing.at" # from [72d595bef8c751173ddf45eeaae5083e97cb1dc1] # to [7f9d094be78574103bd56735a502e162ac836f03] # # patch "testsuite.at" # from [34f3168e47516f1224add46b67a6e7c1455de85b] # to [098047b724b4a9cd4a1c1618759a6b955ca6c390] # --- ChangeLog +++ ChangeLog @@ -1,3 +1,47 @@ +2005-04-24 Derek Scherger + + * app_state.{cc,hh} (app_state): add all_files flag to the constructor + (set_all_files): new method for setting flag + + * basic_io.{cc,hh} (escape): expose public method to quote and + escape file_paths + (push_str_pair): use it internally + + * commands.cc (calculate_restricted_rearrangement): new function + factored out of calculate_restricted_revision + (calculate_restricted_revision): use new function + (struct unknown_itemizer): rename to ... + (struct file_itemizer): ... this; use a path_set rather than a + manifest map; build path sets of unknown and ignored files, rather + than simply printing them + (ls_unknown): adjust to compensate for itemizer changes + (print_inventory): new functions for printing inventory lines from + path sets and rename maps + (inventory): new command for printing inventory of working copy + files + + * manifest.cc (inodeprint_unchanged): new function factored out + from build_restricted_manifest_map + (classify_paths): new function to split paths from an old manifest + into unchanged, changed or missing sets for inventory + (build_restricted_manifest_map): adjust to use + inodeprint_unchanged + * manifest.hh (classify_paths): new public function + + * monotone.1: document new inventory command and associated + --all-files option + + * monotone.cc: add new --all-files option which will be specific + to the inventory command asap + + * monotone.texi (Informative): document new inventory command + (Commands): add manpage entry for inventory + (OPTIONS): add entries for --xargs, -@ and --all-files + + * tests/t_status_missing.at: remove bug priority flag + * tests/t_inventory.at: new test + * testsuite.at: include new test + 2005-04-23 Derek Scherger * (calculate_restricted_revision): remove redundant variables, --- app_state.cc +++ app_state.cc @@ -30,7 +30,7 @@ static string const key_option("key"); app_state::app_state() - : branch_name(""), db(""), stdhooks(true), rcfiles(true), + : branch_name(""), db(""), stdhooks(true), rcfiles(true), all_files(false), search_root("/"), depth(-1) { db.set_app(this); @@ -312,6 +312,12 @@ } void +app_state::set_all_files(bool b) +{ + all_files = b; +} + +void app_state::add_rcfile(utf8 const & filename) { extra_rcfiles.push_back(filename); --- app_state.hh +++ app_state.hh @@ -31,6 +31,7 @@ lua_hooks lua; bool stdhooks; bool rcfiles; + bool all_files; options_map options; utf8 message; utf8 date; @@ -71,6 +72,7 @@ void set_stdhooks(bool b); void set_rcfiles(bool b); + void set_all_files(bool b); void add_rcfile(utf8 const & filename); explicit app_state(); --- basic_io.cc +++ basic_io.cc @@ -34,6 +34,30 @@ in.err(s); } +std::string basic_io::escape(std::string const & s) +{ + std::string escaped; + escaped.reserve(s.size() + 8); + + escaped += "\""; + + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) + { + switch (*i) + { + case '\\': + case '"': + escaped += '\\'; + default: + escaped += *i; + } + } + + escaped += "\""; + + return escaped; +} + basic_io::stanza::stanza() : indent(0) {} @@ -55,24 +79,7 @@ for (std::string::const_iterator i = k.begin(); i != k.end(); ++i) I(std::isalnum(*i) || *i == '_'); - std::string escaped; - escaped.reserve(v.size() + 8); - - for (std::string::const_iterator i = v.begin(); - i != v.end(); ++i) - { - switch (*i) - { - case '\\': - case '"': - escaped += '\\'; - default: - escaped += *i; - } - } - - - entries.push_back(std::make_pair(k, "\"" + escaped + "\"")); + entries.push_back(std::make_pair(k, escape(v))); if (k.size() > indent) indent = k.size(); } --- basic_io.hh +++ basic_io.hh @@ -141,6 +141,8 @@ void err(std::string const & s); }; + std::string escape(std::string const & s); + struct stanza { --- commands.cc +++ commands.cc @@ -25,6 +25,7 @@ #include "app_state.hh" #include "automate.hh" +#include "basic_io.hh" #include "cert.hh" #include "database_check.hh" #include "diff_patch.hh" @@ -533,29 +534,25 @@ } static void -calculate_restricted_revision(app_state & app, - vector const & args, - revision_set & rev, - manifest_map & m_old, - manifest_map & m_new, - change_set::path_rearrangement & excluded) +calculate_restricted_rearrangement(app_state & app, + vector const & args, + manifest_id & old_manifest_id, + revision_id & old_revision_id, + manifest_map & m_old, + path_set & old_paths, + path_set & new_paths, + change_set::path_rearrangement & included, + change_set::path_rearrangement & excluded) { - manifest_id old_manifest_id; - revision_id old_revision_id; - boost::shared_ptr cs(new change_set()); - path_set old_paths, new_paths; change_set::path_rearrangement work; - rev.edges.clear(); - m_old.clear(); - m_new.clear(); - get_base_revision(app, old_revision_id, old_manifest_id, m_old); + extract_path_set(m_old, old_paths); + get_path_rearrangement(work); - extract_path_set(m_old, old_paths); path_set valid_paths(old_paths); extract_rearranged_paths(work, valid_paths); @@ -563,10 +560,33 @@ app.set_restriction(valid_paths, args); - restrict_path_rearrangement(work, cs->rearrangement, excluded, app); + restrict_path_rearrangement(work, included, excluded, app); - apply_path_rearrangement(old_paths, cs->rearrangement, new_paths); + apply_path_rearrangement(old_paths, included, new_paths); +} +static void +calculate_restricted_revision(app_state & app, + vector const & args, + revision_set & rev, + manifest_map & m_old, + manifest_map & m_new, + change_set::path_rearrangement & excluded) +{ + manifest_id old_manifest_id; + revision_id old_revision_id; + boost::shared_ptr cs(new change_set()); + path_set old_paths, new_paths; + + rev.edges.clear(); + m_old.clear(); + m_new.clear(); + + calculate_restricted_rearrangement(app, args, + old_manifest_id, old_revision_id, + m_old, old_paths, new_paths, + cs->rearrangement, excluded); + build_restricted_manifest_map(new_paths, m_old, m_new, app); complete_change_set(m_old, m_new, *cs); @@ -575,7 +595,6 @@ rev.edges.insert(make_pair(old_revision_id, make_pair(old_manifest_id, cs))); - } static void @@ -2008,32 +2027,26 @@ } } -struct unknown_itemizer : public tree_walker +struct file_itemizer : public tree_walker { app_state & app; - manifest_map & man; - bool want_ignored; - unknown_itemizer(app_state & a, manifest_map & m, bool i) - : app(a), man(m), want_ignored(i) {} + path_set & known; + path_set & unknown; + path_set & ignored; + file_itemizer(app_state & a, path_set & k, path_set & u, path_set & i) + : app(a), known(k), unknown(u), ignored(i) {} virtual void visit_file(file_path const & path) { - if (app.restriction_includes(path) && man.find(path) == man.end()) + if (app.restriction_includes(path) && known.find(path) == known.end()) { - if (want_ignored) - { - if (app.lua.hook_ignore_file(path)) - cout << path() << endl; - } - else - { - if (!app.lua.hook_ignore_file(path)) - cout << path() << endl; - } + if (app.lua.hook_ignore_file(path)) + ignored.insert(path); + else + unknown.insert(path); } } }; - static void ls_unknown (app_state & app, bool want_ignored, vector const & args) { @@ -2041,9 +2054,25 @@ revision_set rev; manifest_map m_old, m_new; + path_set known, unknown, ignored; + calculate_restricted_revision(app, args, rev, m_old, m_new); - unknown_itemizer u(app, m_new, want_ignored); + + extract_path_set(m_new, known); + file_itemizer u(app, known, unknown, ignored); walk_tree(u); + + if (want_ignored) + for (path_set::const_iterator i = ignored.begin(); i != ignored.end(); ++i) + { + cout << *i << endl; + } + else + for (path_set::const_iterator i = unknown.begin(); i != unknown.end(); ++i) + { + cout << *i << endl; + } + } static void @@ -2080,7 +2109,86 @@ } } +static void +print_inventory(std::string const & status, + std::string const & suffix, + path_set const & files, + path_set const & excluded) +{ + for (path_set::const_iterator i = files.begin(); i != files.end(); ++i) + { + if (excluded.find(*i) == excluded.end()) + cout << status << " " << basic_io::escape((*i)() + suffix) << endl; + } +} +static void +print_inventory(std::string const & status, + std::string const & suffix, + std::map const & renames, + path_set const & excluded) + +{ + for (std::map::const_iterator i = renames.begin(); + i != renames.end(); ++i) + { + if (excluded.find(i->second) == excluded.end()) + cout << status + << " " << basic_io::escape(i->first() + suffix) + << " " << basic_io::escape(i->second() + suffix) + << endl; + } +} + +CMD(inventory, "informative", "[PATH]...", + "inventory of every file in working copy with associated status") +{ + manifest_id old_manifest_id; + revision_id old_revision_id; + manifest_map m_old; + path_set old_paths, new_paths, empty; + change_set::path_rearrangement included, excluded; + path_set missing, changed, unchanged, unknown, ignored; + + app.require_working_copy(); + + calculate_restricted_rearrangement(app, args, + old_manifest_id, old_revision_id, + m_old, old_paths, new_paths, + included, excluded); + + file_itemizer u(app, new_paths, unknown, ignored); + walk_tree(u); + + classify_paths(app, new_paths, m_old, missing, changed, unchanged); + + print_inventory("!", "", missing, empty); + + // a file may be missing and also added or the target of a rename. or it may + // be added or the target of a rename and also changed. inventory lists each + // file only once with the highest priority status. missing takes precedence + // over added or renamed. added or renamed takes precedence over changed. + + print_inventory("-", "", included.deleted_files, empty); + print_inventory("-", "/", included.deleted_dirs, empty); + + // ensure missing has precedence over renamed + print_inventory("%", "", included.renamed_files, missing); + print_inventory("%", "/", included.renamed_dirs, missing); + + // ensure missing has precedence over added + print_inventory("+", "", included.added_files, missing); + + print_inventory("#", "", changed, empty); + + if (app.all_files) + { + print_inventory("=", "", unchanged, empty); + print_inventory("?", "", unknown, empty); + print_inventory("~", "", ignored, empty); + } +} + CMD(list, "informative", "certs ID\n" "keys [PATTERN]\n" --- manifest.cc +++ manifest.cc @@ -59,7 +59,89 @@ man.insert(manifest_entry(path, file_id(ident))); } +inline static bool +inodeprint_unchanged(inodeprint_map const & ipm, file_path const & path) +{ + inodeprint_map::const_iterator old_ip = ipm.find(path); + if (old_ip != ipm.end()) + { + hexenc ip; + if (inodeprint_file(path, ip) && ip == old_ip->second) + return true; // unchanged + else + return false; // changed or unavailable + } + else + return false; // unavailable +} + void +classify_paths(app_state & app, + path_set const & paths, + manifest_map const & m_old, + path_set & missing, + path_set & changed, + path_set & unchanged) +{ + inodeprint_map ipm; + + if (in_inodeprints_mode()) + { + data dat; + read_inodeprints(dat); + read_inodeprint_map(dat, ipm); + } + + // this code is speed critical, hence the use of inode fingerprints so be + // careful when making changes in here and preferably do some timing tests + + for (path_set::const_iterator i = paths.begin(); i != paths.end(); ++i) + { + if (app.restriction_includes(*i)) + { + // compute the current sha1 id for included files + // we might be able to avoid it, if we have an inode fingerprint... + if (inodeprint_unchanged(ipm, *i)) + { + // the inode fingerprint hasn't changed, so we assume the file + // hasn't either. + manifest_map::const_iterator k = m_old.find(*i); + I(k != m_old.end()); + unchanged.insert(*i); + continue; + } + + // ...ah, well, no good fingerprint, just check directly. + if (file_exists(*i)) + { + hexenc ident; + calculate_ident(*i, ident, app.lua); + manifest_map::const_iterator k = m_old.find(*i); + + if (k != m_old.end()) + { + if (ident == k->second.inner()) + unchanged.insert(*i); + else + changed.insert(*i); + } + + // if the path was not found in the old manifest it must have + // been added or renamed ad it's ignored here + + } + else + missing.insert(*i); + } + else + { + // changes to excluded files are ignored + unchanged.insert(*i); + } + } +} + +void build_restricted_manifest_map(path_set const & paths, manifest_map const & m_old, manifest_map & m_new, @@ -77,25 +159,23 @@ size_t missing_files = 0; + // this code is speed critical, hence the use of inode fingerprints so be + // careful when making changes in here and preferably do some timing tests + for (path_set::const_iterator i = paths.begin(); i != paths.end(); ++i) { if (app.restriction_includes(*i)) { // compute the current sha1 id for included files // we might be able to avoid it, if we have an inode fingerprint... - inodeprint_map::const_iterator old_ip = ipm.find(*i); - if (old_ip != ipm.end()) + if (inodeprint_unchanged(ipm, *i)) { - hexenc ip; - if (inodeprint_file(*i, ip) && ip == old_ip->second) - { - // the inode fingerprint hasn't changed, so we assume the file - // hasn't either. - manifest_map::const_iterator k = m_old.find(*i); - I(k != m_old.end()); - m_new.insert(*k); - continue; - } + // the inode fingerprint hasn't changed, so we assume the file + // hasn't either. + manifest_map::const_iterator k = m_old.find(*i); + I(k != m_old.end()); + m_new.insert(*k); + continue; } // ...ah, well, no good fingerprint, just check directly. --- manifest.hh +++ manifest.hh @@ -74,6 +74,13 @@ class app_state; +void classify_paths(app_state & app, + path_set const & paths, + manifest_map const & m_old, + path_set & missing, + path_set & changed, + path_set & unchanged); + void build_restricted_manifest_map(path_set const & paths, manifest_map const & m_old, manifest_map & m_new, --- monotone.1 +++ monotone.1 @@ -41,6 +41,9 @@ \fBstatus \fI[...]\fP Show status of working copy. .TP +\fBinventory \fI[...]\fP +Show inventory of files in the working copy. +.TP \fBlog\fP \fI[id] \fP Show historical log of revisions, starting from working copy base revision, or \fI[id]\fP if given. @@ -276,7 +279,7 @@ at the specified root directory rather than at the physical root of the filesystem. .TP -\fB-xargs=\fI\fP +\fB--xargs=\fI\fP Inject the contents of the file in place among the command line arguments. This may be useful in case the command line would otherwise become too long for your system. This option can be used @@ -284,6 +287,11 @@ .TP \fB-@ \fI\fP An alias for \fB--xargs=\fI\fP +.TP +\fB--all-files\fP +Include every file available in the working copy in the inventory +list. By default inventory only lists ``interesting'' files. This option +is specific to the @command{inventory} command. .SH ENVIRONMENT .TP --- monotone.cc +++ monotone.cc @@ -47,6 +47,7 @@ #define OPT_ARGFILE 18 #define OPT_DATE 19 #define OPT_AUTHOR 20 +#define OPT_ALL_FILES 21 // main option processing and exception handling code @@ -77,6 +78,7 @@ {"root", 0, POPT_ARG_STRING, &argstr, OPT_ROOT, "limit search for working copy to specified root", NULL}, {"depth", 0, POPT_ARG_LONG, &arglong, OPT_DEPTH, "limit the log output to the given number of entries", NULL}, {"xargs", '@', POPT_ARG_STRING, &argstr, OPT_ARGFILE, "insert command line arguments taken from the given file", NULL}, + {"all-files", 0, POPT_ARG_NONE, NULL, OPT_ALL_FILES, "inventory all working copy files", NULL}, { NULL, 0, 0, NULL, 0, NULL, NULL } }; @@ -349,6 +351,10 @@ utf8(string(argstr)))); break; + case OPT_ALL_FILES: + app.set_all_files(true); + break; + case OPT_HELP: default: requested_help = true; --- monotone.texi +++ monotone.texi @@ -3623,7 +3623,63 @@ copy. Specifying only the pathname "." will restrict @command{status} to files changed within the current subdirectory of the working copy. address@hidden monotone inventory address@hidden monotone inventory @var{pathname...} +This command prints the ``inventory'' of files in a working copy. Each +file is prefixed by a single character indicating the status of the +associated file. address@hidden address@hidden ! missing file address@hidden - dropped file address@hidden % renamed file address@hidden + added file address@hidden # changed file address@hidden = unchanged file address@hidden ? unknown file address@hidden ~ ignored file address@hidden itemize + +By default the @command{inventory} command lists only ``interesting'' +files, considered to be those with a status of missing, dropped, +renamed, added or changed. If the @option{--all-files} option is +specified the @command{inventory} command lists every file in the +working copy, including those with a status of unchanged, unknown and +ignored. + +Specifying optional @var{pathname...} arguments to the @command{inventory} +command restricts the set of changes that are visible and results in +only a partial inventory of the working copy. Changes to files not included +in the specified set of pathnames will be ignored. + +From within a subdirectory of the working copy the @command{inventory} +command will, by default, include @emph{all changes} in the working +copy. Specifying only the pathname "." will restrict @command{inventory} +to files changed within the current subdirectory of the working copy. + +If the @option{--all-files} option is specified the @command{inventory} +command will list all files in the working copy. However, the changes +that are will be restricted as described above. + +Renamed files have both the old and new name of the file listed on +the same line in the inventory, in that order. + +A file may be missing and also added or the target of a rename, or it +may be added or the target of a rename and also changed. The address@hidden command lists each file only once and in these cases +the status with the highest priority will be displayed. The missing +status takes precedence over both added or renamed and these values take +precedence over the changed status. + +Since pathnames may contain spaces, quotes and other ``special'' +characters, the @command{inventory} command lists quoted filenames to +avoid any ambiguities. + +Full support for versioned directories is not yet complete and the address@hidden command will only list entries for renamed or +dropped directories. Directory entries will be designated by pathnames +ending with the "/" character. + @item monotone log @itemx monotone log address@hidden @itemx monotone log @var{id} @@ -5902,6 +5958,10 @@ Show status of working copy. @comment TROFF INPUT: .TP address@hidden @b{inventory} @i{[...]} +Show inventory of files in working copy. address@hidden TROFF INPUT: .TP + @item @b{log} @i{[id]} Show historical log of revisions, starting from working copy base revision, or @i{[id]} if given. @@ -6202,6 +6262,20 @@ at the specified root directory rather than at the physical root of the filesystem. address@hidden @address@hidden} +Inject the contents of the file in place among the command line +arguments. This may be useful in case the command line would otherwise +become too long for your system. This option can be used more than once +if needed. + address@hidden @b{-@@} @i{} +An alias for @address@hidden}. + address@hidden @b{--all-files} +Include every file available in the working copy in the inventory +list. By default inventory only lists ``interesting'' files. This option +is specific to the @command{inventory} command. + @comment TROFF INPUT: .SH ENVIRONMENT @end table --- tests/t_inventory.at +++ tests/t_inventory.at @@ -0,0 +1,93 @@ +AT_SETUP([working copy inventory]) +MONOTONE_SETUP + +AT_DATA(inventory_hooks.lua, [ + +function ignore_file(name) + if (string.find(name, "%~$")) then return true end + return false +end +]) + +ADD_FILE(missing, [missing +]) +ADD_FILE(dropped, [dropped +]) +ADD_FILE(original, [original +]) +ADD_FILE(unchanged, [unchanged +]) + +ADD_FILE(changed, [changed +]) + +COMMIT(testbranch) + +ADD_FILE(added, [added +]) +AT_DATA(unknown, [unknown +]) +AT_DATA(ignored~, [ignored~ +]) + +AT_CHECK(rm missing) +AT_CHECK(mv original renamed) +AT_DATA(changed, [something has changed +]) + +AT_CHECK(MONOTONE add added, [], [ignore], [ignore]) +AT_CHECK(MONOTONE rename original renamed, [], [ignore], [ignore]) +AT_CHECK(MONOTONE drop dropped, [], [ignore], [ignore]) + +# inventory by default only lists changed files + +AT_CHECK(MONOTONE inventory, [], [stdout], [ignore]) + +AT_CHECK(grep '^! "missing"' stdout, [], [ignore], [ignore]) +AT_CHECK(grep '^+ "added"' stdout, [], [ignore], [ignore]) +AT_CHECK(grep '^- "dropped"' stdout, [], [ignore], [ignore]) +AT_CHECK(grep '^% "original" "renamed"' stdout, [], [ignore], [ignore]) +#AT_CHECK(grep '^# "changed"' stdout, [], [ignore], [ignore]) + +AT_CHECK(grep '^= "unchanged"' stdout, [1], [ignore], [ignore]) +AT_CHECK(grep '^? "unknown"' stdout, [1], [ignore], [ignore]) +AT_CHECK(grep '^~ "ignored~"' stdout, [1], [ignore], [ignore]) + +# with --all-files they're all listed + +AT_CHECK(MONOTONE inventory --all-files --rcfile inventory_hooks.lua, [], [stdout], [ignore]) + +AT_CHECK(grep '^= "unchanged"' stdout, [], [ignore], [ignore]) +AT_CHECK(grep '^? "unknown"' stdout, [], [ignore], [ignore]) +AT_CHECK(grep '^~ "ignored~"' stdout, [], [ignore], [ignore]) + +# renamed takes precedence over changed + +AT_DATA(renamed, [renamed and changed +]) + +AT_CHECK(MONOTONE inventory, [], [stdout], [ignore]) + +#AT_CHECK(grep '^# "renamed"' stdout, [1], [ignore], [ignore]) +AT_CHECK(grep '^% "original" "renamed"' stdout, [], [ignore], [ignore]) + +# missing renamed and added files + +AT_CHECK(rm renamed added) + +AT_CHECK(MONOTONE inventory, [], [stdout], [ignore]) + +AT_CHECK(grep '^! "added"' stdout, [], [ignore], [ignore]) +AT_CHECK(grep '^! "renamed"' stdout, [], [ignore], [ignore]) +AT_CHECK(grep '^% "original" "renamed"' stdout, [1], [ignore], [ignore]) + +# need tests for deleted and renamed directories, once these actually work! + +# check the inventory finds the right number of files + +I_FILES=`MONOTONE inventory --all-files | wc -l` +F_FILES=`find . -type f | wc -l` + +AT_CHECK(test $I_FILES -eq $F_FILES) + +AT_CLEANUP --- tests/t_status_missing.at +++ tests/t_status_missing.at @@ -1,4 +1,4 @@ -AT_SETUP([(normal) status with missing files]) +AT_SETUP([status with missing files]) MONOTONE_SETUP ADD_FILE(testfile1, [foo --- testsuite.at +++ testsuite.at @@ -578,3 +578,4 @@ m4_include(tests/t_multiple_heads_msg.at) m4_include(tests/t_diff_currev.at) m4_include(tests/t_normalized_filenames.at) +m4_include(tests/t_inventory.at)