# # add_file "tests/t_add_stomp_file.at" # # patch "ChangeLog" # from [6a8ce307ea6199b0bb97230ba867d3fe0a874611] # to [0b2b361c69e5acd4d580f049c7aa95f6571162dc] # # patch "commands.cc" # from [e24b9d3f4cf1db10ea68ce3733c1a8bdafb5371d] # to [e351cc76be3dbe3eaa3d9a35f69a3efe5b09c414] # # patch "database.cc" # from [221715fff7bde7114e97c033876cfd103d9e84ce] # to [39d6b99c913576436deb56633c84e9ce3c96f73d] # # patch "monotone.texi" # from [412d8c60e2a7e06ae07a6b8350393421d7f87b2b] # to [939f56de6aac68a630492031939ce4654f093203] # # patch "tests/t_add_stomp_file.at" # from [] # to [ff7f16f34c202ec044ad232af78786bd119b5e98] # # patch "tests/t_no_rename_overwrite.at" # from [345db00bfc31ac8c23f0735fe890c4d869e37448] # to [1fe86d8eeda1180521602e9b66628aadad9b75ac] # # patch "testsuite.at" # from [b0e5437b101dacfab83fac745189a2a60355bd78] # to [5c3edfba27e5b828d6080f873f035114c54e7823] # # patch "transforms.cc" # from [92b2495dcc4067794d6b3cd58c0f30f865a08a28] # to [61593e946306f8bd5926c02fca2ea683e510d271] # # patch "work.cc" # from [2edb3fc960fee8c34b1abb13dff648ec63eeb40f] # to [dd1b1800d78f63c9f5615622d7c638c01c2f7459] # --- ChangeLog +++ ChangeLog @@ -11,6 +11,44 @@ * tests/t_database_check_minor.at: New test. * testsuite.at: Add it. +2005-04-17 Richard Levitte + + * transforms.cc (glob_to_regexp): New function that takes a glob + expression and transforms it into a regexp. This will be useful + for globbing branch expressions when collections are exchanged to + branch globs and regexps. + (glob_to_regexp_test): A unit test for glob_to_regexp(). + +2005-04-16 Emile Snyder + + * tests/t_add_stomp_file.at: New test for failing case. + If you have a file foo in your working dir (not monotone + controlled) and someone else adds a file foo and commits, + update should at least warn you before stomping your + non-recoverable foo file. + * testsuite.at: Add it. + +2005-04-17 Matt Johnston + + * commands.cc: warn that dropkey won't truly erase the privkey + from the database + * monotone.texi: same + +2005-04-17 Matt Johnston + + * database.cc: mention that it could be the filesystem that + is full in the SQLITE_FULL error message + +2005-04-16 Derek Scherger + + * work.cc (known_preimage_path): rename to... + (known_path): this, since it's image agnostic + (build_deletions): update for renamed function + (build_rename): ensure rename source exists in current revision + and rename target does not exist in current revision + + * tests/t_no_rename_overwrite.at: un-XFAIL + 2005-04-16 Nathaniel Smith * app_state.{cc,hh} (set_author, set_date): New methods. --- commands.cc +++ commands.cc @@ -1184,7 +1184,10 @@ if (app.db.private_key_exists(ident)) { - P(F("dropping private key '%s' from database\n") % ident); + P(F("dropping private key '%s' from database\n\n") % ident); + W(F("the private key data may not have been erased from the")); + W(F("database. it is recommended that you use 'db dump' and")); + W(F("'db load' to be sure.")); app.db.delete_private_key(ident); key_deleted = true; } --- database.cc +++ database.cc @@ -589,7 +589,7 @@ break; case SQLITE_FULL: - throw oops("Insertion failed because database is full"); + throw oops("Insertion failed because database (or filesystem) is full"); break; case SQLITE_CANTOPEN: --- monotone.texi +++ monotone.texi @@ -3813,7 +3813,10 @@ This command drops the public and/or private key. If both exist, both are dropped, if only one exists, it is dropped. This command should be used with caution as changes are irreversible without a backup of -the key(s) that were dropped. +the key(s) that were dropped. Note also that the private key is not +guaranteed to actually be erased from your database file - if you are +going to make the database file public, you should use 'db dump' +and 'db load' to import into a fresh database. @item monotone chkeypass @var{id} --- tests/t_add_stomp_file.at +++ tests/t_add_stomp_file.at @@ -0,0 +1,44 @@ +AT_SETUP([make sure update does not stomp non-monotone file]) +MONOTONE_SETUP + +# This test is a bug report +AT_XFAIL_IF(true) + +# 1. Alice checks out project, creates file foo +# 2. Bob checks out project, creates foo, adds foo, and commits +# 3. Now Alice does an update +# +# monotone should warn her before stomping her non-revision controlled 'foo' file +# + +AT_DATA(initial, [some initial data +]) + +AT_DATA(foo.alice, [foo +not revision controlled +]) + +AT_DATA(foo.bob, [foo +checked into project +]) + +# Alice make project, writes foo, but doesn't check it in +AT_CHECK(mkdir alicewd) +AT_CHECK(cp initial alicewd/initial) +AT_CHECK(MONOTONE --branch=testbranch setup alicewd, [], [ignore], [ignore]) +AT_CHECK( (cd alicewd; MONOTONE --branch=testbranch --root=. add initial), [], [ignore], [ignore]) +AT_CHECK( (cd alicewd; MONOTONE --branch=testbranch --root=. commit -m 'initial commit'), [], [ignore], [ignore]) +AT_CHECK(cp foo.alice alicewd/foo) + +# Bob does add of file foo, and commits +AT_CHECK(MONOTONE --branch=testbranch checkout bobwd, [], [ignore], [ignore]) +AT_CHECK(cp foo.bob bobwd/foo) +AT_CHECK( (cd bobwd; MONOTONE --branch=testbranch --root=. add foo), [], [ignore], [ignore]) +AT_CHECK( (cd bobwd; MONOTONE --branch=testbranch --root=. commit -m 'bob commit'), [], [ignore], [ignore]) +REV=`BASE_REVISION` + +# Alice does her update, discovers foo has been stomped! +AT_CHECK( (cd alicewd; MONOTONE --branch=testbranch --root=. update $REV), [], [ignore], [ignore]) +AT_CHECK(cmp foo.alice alicewd/foo) + +AT_CLEANUP --- tests/t_no_rename_overwrite.at +++ tests/t_no_rename_overwrite.at @@ -1,9 +1,6 @@ AT_SETUP([rename cannot overwrite files]) MONOTONE_SETUP -# This test is a bug report. -AT_XFAIL_IF(true) - # "rename" needs to check that it isn't overwriting existing # files/directories. @@ -19,19 +16,18 @@ ADD_FILE("rename_dir/file", [bar bar ]) -AT_CHECK(MONOTONE rename rename_file target_file, [3], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_file target_dir, [3], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_dir target_file, [3], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_dir target_dir, [3], [ignore], [ignore]) +AT_CHECK(MONOTONE rename unknown_file other_file, [1], [ignore], [ignore]) +AT_CHECK(MONOTONE rename rename_file target_file, [1], [ignore], [ignore]) +AT_CHECK(MONOTONE rename rename_file target_dir, [1], [ignore], [ignore]) +AT_CHECK(MONOTONE rename rename_dir target_file, [1], [ignore], [ignore]) +AT_CHECK(MONOTONE rename rename_dir target_dir, [1], [ignore], [ignore]) COMMIT(testbranch) -AT_CHECK(MONOTONE rename rename_file target_file, [0], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_file target_dir, [0], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_dir target_file, [0], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_dir target_dir, [0], [ignore], [ignore]) +AT_CHECK(MONOTONE rename unknown_file other_file, [1], [ignore], [ignore]) +AT_CHECK(MONOTONE rename rename_file target_file, [1], [ignore], [ignore]) +AT_CHECK(MONOTONE rename rename_file target_dir, [1], [ignore], [ignore]) +AT_CHECK(MONOTONE rename rename_dir target_file, [1], [ignore], [ignore]) +AT_CHECK(MONOTONE rename rename_dir target_dir, [1], [ignore], [ignore]) -# this should fail -COMMIT(testbranch) - AT_CLEANUP --- testsuite.at +++ testsuite.at @@ -562,4 +562,5 @@ m4_include(tests/t_add_vs_commit.at) m4_include(tests/t_update_nonexistent.at) m4_include(tests/t_override_author_date.at) +m4_include(tests/t_add_stomp_file.at) m4_include(tests/t_database_check_minor.at) --- transforms.cc +++ transforms.cc @@ -718,7 +718,145 @@ dst += linesep_str; } +// glob_to_regexp converts a sh file glob to a regexp. The regexp should +// be usable by the Boost regexp library. +// +// Pattern tranformation: +// +// - Any character except those described below are copied as they are. +// - The backslash (\) escapes the following character. The escaping +// backslash is copied to the regexp along with the following character. +// - * is transformed to .* in the regexp. +// - ? is transformed to . in the regexp. +// - { is transformed to ( in the regexp, unless within [ and ]. +// - } is transformed to ) in the regexp, unless within [ and ]. +// - , is transformed to | in the regexp, if within { and } and not +// within [ and ]. +// - ^ is escaped unless it comes directly after an unescaped [. +// - ! is transformed to ^ in the regexp if it comes directly after an +// unescaped [. +// - ] directly following an unescaped [ is escaped. +string glob_to_regexp(const string & glob) +{ + struct bad_glob { + bad_glob() : what("Bad glob syntax") {} + string what; + }; + + int in_braces = 0; // counter for levels if {} + bool in_brackets = false; // flags if we're inside a [], which + // has higher precedence than {}. + // Also, [ is accepted inside [] unescaped. + bool this_was_opening_bracket = false; + string tmp; + + tmp.reserve(glob.size() * 2); + #ifdef BUILD_UNIT_TESTS + cerr << "DEBUG[glob_to_regexp]: input = \"" << glob << "\"" << endl; +#endif + + for (string::const_iterator i = glob.begin(); i != glob.end(); ++i) + { + char c = *i; + bool last_was_opening_bracket = this_was_opening_bracket; + this_was_opening_bracket = false; + + // Special case ^ and ! at the beginning of a [] expression. + if (in_brackets && last_was_opening_bracket + && (c == '!' || c == '^')) + { + tmp += '^'; + if (++i == glob.end()) + break; + c = *i; + } + + if (c == '\\') + { + tmp += c; + if (++i == glob.end()) + break; + tmp += *i; + } + else if (in_brackets) + { + switch(c) + { + case ']': + if (!last_was_opening_bracket) + { + in_brackets = false; + tmp += c; + break; + } + // Trickling through to the standard character conversion, + // because ] as the first character of a set is regarded as + // a normal character. + default: + if (!(isalnum(c) || c == '_')) + { + tmp += '\\'; + } + tmp += c; + break; + } + } + else + { + switch(c) + { + case '*': + tmp += ".*"; + break; + case '?': + tmp += '.'; + break; + case '{': + in_braces++; + tmp += '('; + break; + case '}': + if (in_braces == 0) + throw bad_glob(); + tmp += ')'; + in_braces--; + break; + case '[': + in_brackets = true; + this_was_opening_bracket = true; + tmp += c; + break; + case ',': + if (in_braces > 0) + { + tmp += '|'; + break; + } + // Trickling through to default: here, since a comma outside of + // brace notation is just a normal character. + default: + if (!(isalnum(c) || c == '_')) + { + tmp += '\\'; + } + tmp += c; + break; + } + } + } + + if (in_braces != 0 || in_brackets) + throw bad_glob(); + +#ifdef BUILD_UNIT_TESTS + cerr << "DEBUG[glob_to_regexp]: output = \"" << tmp << "\"" << endl; +#endif + + return tmp; +} + +#ifdef BUILD_UNIT_TESTS #include "unit_tests.hh" static void @@ -1004,6 +1142,15 @@ check_idna_encoding(); } +static void glob_to_regexp_test() +{ + BOOST_CHECK(glob_to_regexp("abc,v") == "abc\\,v"); + BOOST_CHECK(glob_to_regexp("foo[12m,]") == "foo[12m\\,]"); + // A full fledged, use all damn features test... + BOOST_CHECK(glob_to_regexp("foo.{bar*,cookie?{haha,hehe[^\\123!,]}}[!]a^b]") + == "foo\\.(bar.*|cookie.(haha|hehe[^\\123\\!\\,]))[^\\]a\\^b]"); +} + void add_transform_tests(test_suite * suite) { @@ -1015,6 +1162,7 @@ suite->add(BOOST_TEST_CASE(&join_lines_test)); suite->add(BOOST_TEST_CASE(&strip_ws_test)); suite->add(BOOST_TEST_CASE(&encode_test)); + suite->add(BOOST_TEST_CASE(&glob_to_regexp_test)); } #endif // BUILD_UNIT_TESTS --- work.cc +++ work.cc @@ -88,9 +88,9 @@ } static bool -known_preimage_path(file_path const & p, - path_set const & ps, - bool & path_is_directory) +known_path(file_path const & p, + path_set const & ps, + bool & path_is_directory) { std::string path_as_dir = p() + "/"; for (path_set::const_iterator i = ps.begin(); i != ps.end(); ++i) @@ -126,7 +126,7 @@ N((*i)() != "", F("invalid path ''")); - if (! known_preimage_path(*i, ps, dir_p)) + if (! known_path(*i, ps, dir_p)) { P(F("skipping %s, not currently tracked\n") % *i); continue; @@ -163,16 +163,17 @@ extract_path_set(man, ps); apply_path_rearrangement(pr, ps); - bool dir_p = false; + bool src_dir_p = false; + bool dst_dir_p = false; - if (! known_preimage_path(src, ps, dir_p)) - { - P(F("skipping %s, not currently tracked\n") % src); - return; - } + N(known_path(src, ps, src_dir_p), + F("%s does not exist in current revision\n") % src); + N(!known_path(dst, ps, dst_dir_p), + F("%s already exists in current revision\n") % dst); + P(F("adding %s -> %s to working copy rename set\n") % src % dst); - if (dir_p) + if (src_dir_p) pr_new.renamed_dirs.insert(std::make_pair(src, dst)); else pr_new.renamed_files.insert(std::make_pair(src, dst));