[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Monotone-devel] [PATCH] new selectors: "earlier or equal" and ?late
From: |
rghetta |
Subject: |
Re: [Monotone-devel] [PATCH] new selectors: "earlier or equal" and ?later" |
Date: |
Thu, 12 May 2005 08:18:48 +0200 |
User-agent: |
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.6) Gecko/20050322 |
Ok, I've taken care of the tabs and rewritten the tests using RAW_MONOTONE.
On the normdate function: without it the date comparison simply doesn't
work.
As an example try a db execute of
'SELECT id FROM revision_certs WHERE name="date" AND unbase64(value) >
"2005-05-13T00:00:00"'
it should return no rows, instead returns all the certs on the database,
and changing the string has no effect on the result (using <, however,
yields no rows)
This time I digged deeper, trying to understand why the comparison
doesn't work.
The problem is unbase64(): it does return (correctly IMHO) a blob and
apparently sqlite don't allows mixing blobs and strings. Substring
searching works correctly, put ordering operators not.
I guess sqlite encodes something in the blob making it "greater" than a
string.
Perhaps sqlite devs should made aware of this behaviour; for now you
need a sql function to handle these comparisons.
One of such functions is normdate() of my previous patch. Another is an
unbase64_text() identical to unbase64() but returning a text.
While the latter has a better syntax and could be generally useful, it
potentially confusing, since it's not obvious which unbase* function to use.
Besides, if this behaviour is a bug of sqlite as I suspect, sooner or
later the function will be useless.
Thus I still favor the normdate() approach, but either one will be fine
and attached you will find TWO patches: one with normdate(), and one
with unbase64_text(). Choose as you wish :)
Riccardo
#
# add_file "tests/t_selector_later_earlier.at"
#
# patch "database.cc"
# from [8bd9d2d49322d496a8cd4ed3e3a8f802eb0d9ed2]
# to [28cb6413e5681faca9907198213c1659da28d963]
#
# patch "lua.cc"
# from [51659bca46465ce818dc3ed0adf1a81568c64cf3]
# to [76cfc26a5a3d0c64a9f244d78ad51c8975aa8d52]
#
# patch "lua.hh"
# from [5840b51a224c1bf2e25d448b00c2a3c48e2fd1dc]
# to [a1b3877535049b77b678d864354d030f8433854c]
#
# patch "monotone.texi"
# from [1043aaa99cad1d0060f75058661a71ad85c12ac5]
# to [8a3ea89c980dfe06b6323d42eb27a388908cec28]
#
# patch "selectors.cc"
# from [e0fe016aca3d1fcc1c64d3f7543b4e2bc7419e64]
# to [9bffb57576eaaf2b915f3ca01182b9c9d38b26f7]
#
# patch "selectors.hh"
# from [0a31a5fa5e4adaf3e65f8c0935fb58e88cee0aac]
# to [5395660518db493e48ec71989769e70515e8697e]
#
# patch "std_hooks.lua"
# from [4ed8fde74ed25fbbc5e693d3acd81c468768fed0]
# to [e546fd6e63e0bcf7b31fe90cedf3fe85e2a3d534]
#
# patch "tests/t_selector_later_earlier.at"
# from []
# to [fafef1139ab46496ae3b55a18ede48f2afa6baab]
#
# patch "testsuite.at"
# from [6b0d1c1e97cbb6def87352d063db38857a432f48]
# to [9c4c0555965abbeb8d070da8429bca44050cb2d8]
#
--- database.cc
+++ database.cc
@@ -167,6 +167,37 @@
sqlite3_result_blob(f, unpacked().c_str(), unpacked().size(),
SQLITE_TRANSIENT);
}
+static void
+sqlite3_normdate_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
+{
+ if (nargs != 1)
+ {
+ sqlite3_result_error(f, "need exactly 1 arg to normdate()", -1);
+ return;
+ }
+ const char *indate=sqlite3_value_text_s(args[0]);
+
+ // input should be something like "2005-05-01T22:33:44"
+ if (strlen(indate)!=19 || indate[4]!='-' || indate[7]!='-' ||
(indate[10]!='T' && indate[10]!=' ') || indate[13]!=':' || indate[16]!=':')
+ {
+ sqlite3_result_error(f, "invalid date", -1);
+ return;
+ }
+
+ // normalizing the input date as "20050501.223344"
+ char buf[15];
+ memmove(buf, indate, 4);
+ memmove(buf+4, indate+5, 2);
+ memmove(buf+6, indate+8, 5);
+ buf[8]='.';
+ memmove(buf+11, indate+14, 2);
+ memmove(buf+13, indate+17, 2);
+ buf[15]='\0';
+ double numdate=atof(buf);
+
+ sqlite3_result_double(f, numdate);
+}
+
void
database::set_app(app_state * app)
{
@@ -1754,6 +1785,11 @@
SQLITE_UTF8, NULL,
&sqlite3_unpack_fn,
NULL, NULL) == 0);
+
+ I(sqlite3_create_function(sql(), "normdate", 1,
+ SQLITE_UTF8, NULL,
+ &sqlite3_normdate_fn,
+ NULL, NULL) == 0);
}
void
@@ -2151,6 +2187,8 @@
s = branch_cert_name;
break;
case selectors::sel_date:
+ case selectors::sel_later:
+ case selectors::sel_earlier:
s = date_cert_name;
break;
case selectors::sel_tag:
@@ -2198,7 +2236,7 @@
lim += (F("WHERE id GLOB '%s*'")
% i->second).str();
}
- else if (i->first == selectors::sel_cert)
+ else if (i->first == selectors::sel_cert)
{
if (i->second.length() > 0)
{
@@ -2239,9 +2277,19 @@
{
string certname;
selector_to_certname(i->first, certname);
- lim += "SELECT id FROM revision_certs ";
- lim += (F("WHERE name='%s' AND unbase64(value) glob '*%s*'")
- % certname % i->second).str();
+ lim += (F("SELECT id FROM revision_certs WHERE name='%s' AND ")
% certname).str();
+ switch (i->first)
+ {
+ case selectors::sel_earlier:
+ lim += (F("normdate(unbase64(value)) <= normdate('%s')") %
i->second).str();
+ break;
+ case selectors::sel_later:
+ lim += (F("normdate(unbase64(value)) > normdate('%s')") %
i->second).str();
+ break;
+ default:
+ lim += (F("unbase64(value) glob '*%s*'") % i->second).str();
+ break;
+ }
}
}
}
@@ -2279,6 +2327,8 @@
query += (F(" AND (id IN %s)") % lim).str();
}
+ //std::cerr << query << std::endl; // debug expr
+
results res;
fetch(res, one_col, any_rows, query.c_str());
for (size_t i = 0; i < res.size(); ++i)
@@ -2444,4 +2494,3 @@
{
committed = true;
}
-
--- lua.cc
+++ lua.cc
@@ -648,6 +648,20 @@
}
bool
+lua_hooks::hook_expand_date(std::string const & sel,
+ std::string & exp)
+{
+ exp.clear();
+ bool res= Lua(st)
+ .func("expand_date")
+ .push_str(sel)
+ .call(1,1)
+ .extract_str(exp)
+ .ok();
+ return res && exp.size();
+}
+
+bool
lua_hooks::hook_get_branch_key(cert_value const & branchname,
rsa_keypair_id & k)
{
--- lua.hh
+++ lua.hh
@@ -39,6 +39,7 @@
// cert hooks
bool hook_expand_selector(std::string const & sel, std::string & exp);
+ bool hook_expand_date(std::string const & sel, std::string & exp);
bool hook_get_branch_key(cert_value const & branchname, rsa_keypair_id & k);
bool lua_hooks::hook_get_priv_key(rsa_keypair_id const & k,
base64< arc4<rsa_priv_key> > & priv_key );
--- monotone.texi
+++ monotone.texi
@@ -2292,7 +2292,20 @@
@code{branch} certs where the cert value begins with @code{net.venge}.
@item Date selection
Uses selector type @code{d}. For example, @code{d:2004-04} matches
address@hidden certs where the cert value begins with @code{2004-04}.
address@hidden certs where the cert value begins with
address@hidden This selector also accepts expanded date syntax (see below).
address@hidden "Earlier or equal than" selection
+Uses selector type @code{e}. For example, @code{e:2004-04-25} matches
address@hidden certs where the cert value is less or equal than
address@hidden:00:00}. If the time component is unspecified,
+monotone will assume 00:00:00. This selector also accepts expanded date
+syntax (see below)
address@hidden "Later than" selection
+Uses selector type @code{l}. For example, @code{l:2004-04-25} matches
address@hidden certs where the cert value is strictly less than
address@hidden:00:00}. If the time component is unspecified,
+monotone will assume 00:00:00. This selector also accepts expanded date
+syntax (see below)
@item Identifier selection
Uses selector type @code{i}. For example, @code{i:0f3a} matches
revision IDs which begin with @code{0f3a}.
@@ -2325,6 +2338,37 @@
addresses, branch names, or date specifications. For the complete
source code of the hook, see @ref{Hook Reference}.
address@hidden Expanding dates
+
+All date-related selectors (@code{d}, @code{e}, @code{l}) support an
+english-like syntax similar to CVS. This syntax is expanded to the
+numeric format by a lua hook: @code{expand_date}.
+The allowed date formats are:
address@hidden @asis
+
address@hidden now
+Expands to the current date and time.
address@hidden today
+Expands to today's date. @code{e} and @code{l} selectors assume time 00:00:00
address@hidden yesterday
+Expands to yesterday's date. @code{e} and @code{l} selectors assume
+time 00:00:00
address@hidden <number> @{minute|address@hidden <ago>
+Expands to today date and time, minus the specified @code{number} of
+minutes|hours.
address@hidden <number> @{day|week|month|address@hidden <ago>
+Expands to today date, minus the specified @code{number} of
+days|weeks|months|years. @code{e} and @code{l} selectors assume time
+00:00:00
address@hidden <year>-<month>[-day[Thour:minute:second]]
+Expands to the supplied year/month. The day and time component are
+optional. If missing, @code{e} and @code{l} selectors assume the first
+day of month and time 00:00:00.
+The time component, if supplied, must be complete to the second.
address@hidden table
+
+For the complete source code of the hook, see @ref{Hook Reference}.
+
@heading Typeless selection
If, after expansion, a selector still has no type, it is matched as a
@@ -5356,11 +5400,10 @@
@group
function expand_selector(str)
- -- simple date patterns
- if string.find(str, "^19%d%d%-%d%d")
- or string.find(str, "^20%d%d%-%d%d")
+ -- something which looks like a generic cert pattern
+ if string.find(str, "^[^=]*=.*$")
then
- return ("d:" .. str)
+ return ("c:" .. str)
end
-- something which looks like an email address
@@ -5381,11 +5424,55 @@
return ("i:" .. str)
end
+ -- tries to expand as a date
+ local dtstr = expand_date(str)
+ if dtstr ~= nil
+ then
+ return ("d:" .. dtstr)
+ end
+
+ return nil
+end
address@hidden group
address@hidden smallexample
+
address@hidden expand_date (@var{str})
+
+Attempts to expand @var{str} as a date expression. Expansion means recognizing
+and interpreting special words such as @code{yesterday} or @code{6
+months ago} and converting them into well formed date expressions. For more
+detail on the use of selectors, see @ref{Selectors}. The default
+definition of this hook is:
+
address@hidden
address@hidden
+function expand_date(str)
+ -- simple date patterns
+ if string.find(str, "^19%d%d%-%d%d")
+ or string.find(str, "^20%d%d%-%d%d")
+ then
+ return (str)
+ end
+
+ -- "now"
+ if str == "now"
+ then
+ local t = os.time(os.date('!*t'))
+ return os.date("%FT%T", t)
+ end
+
+ -- today don't uses the time
+ if str == "today"
+ then
+ local t = os.time(os.date('!*t'))
+ return os.date("%F", t)
+ end
+
-- "yesterday", the source of all hangovers
if str == "yesterday"
then
local t = os.time(os.date('!*t'))
- return os.date("d:%F", t - 86400)
+ return os.date("%F", t - 86400)
end
-- "CVS style" relative dates such as "3 weeks ago"
@@ -5397,21 +5484,24 @@
month = 2678400;
year = 31536000
@}
- local pos, len, n, type = string.find(str, "(%d+)
- ([minutehordaywk]+)s? ago")
+ local pos, len, n, type = string.find(str, "(%d+) ([minutehordaywk]+)s?
ago")
if trans[type] ~= nil
then
local t = os.time(os.date('!*t'))
- return os.date("d:%F", t - (n * trans[type]))
+ if trans[type] <= 3600
+ then
+ return os.date("%FT%T", t - (n * trans[type]))
+ else
+ return os.date("%F", t - (n * trans[type]))
+ end
end
-
+
return nil
end
@end group
@end smallexample
-
@item get_system_linesep ()
Returns a string which defines the default system line separator.
--- selectors.cc
+++ selectors.cc
@@ -22,9 +22,9 @@
L(F("decoding selector '%s'\n") % sel);
+ std::string tmp;
if (sel.size() < 2 || sel[1] != ':')
{
- std::string tmp;
if (!app.lua.hook_expand_selector(sel, tmp))
{
L(F("expansion of selector '%s' failed\n") % sel);
@@ -58,11 +58,35 @@
case 'c':
type = sel_cert;
break;
+ case 'l':
+ type = sel_later;
+ break;
+ case 'e':
+ type = sel_earlier;
+ break;
default:
W(F("unknown selector type: %c\n") % sel[0]);
break;
}
sel.erase(0,2);
+
+ /* a selector date-related should be validated */
+ if (sel_date==type || sel_later==type || sel_earlier==type)
+ {
+ N (app.lua.hook_expand_date(sel, tmp),
+ F ("selector '%s' is not a valid date\n") % sel);
+
+ if (tmp.size()<8 && (sel_later==type || sel_earlier==type))
+ tmp += "-01T00:00:00";
+ else if (tmp.size()<11 && (sel_later==type || sel_earlier==type))
+ tmp += "T00:00:00";
+ N(tmp.size()==19 || sel_date==type, F ("selector '%s' is not a
valid date (%s)\n") % sel % tmp);
+ if (sel != tmp)
+ {
+ P (F ("expanded date '%s' -> '%s'\n") % sel % tmp);
+ sel = tmp;
+ }
+ }
}
}
--- selectors.hh
+++ selectors.hh
@@ -25,6 +25,8 @@
sel_tag,
sel_ident,
sel_cert,
+ sel_earlier,
+ sel_later,
sel_unknown
}
selector_type;
--- std_hooks.lua
+++ std_hooks.lua
@@ -422,13 +422,6 @@
return ("c:" .. str)
end
- -- simple date patterns
- if string.find(str, "^19%d%d%-%d%d")
- or string.find(str, "^20%d%d%-%d%d")
- then
- return ("d:" .. str)
- end
-
-- something which looks like an email address
if string.find(str, "address@hidden")
then
@@ -447,11 +440,44 @@
return ("i:" .. str)
end
+ -- tries to expand as a date
+ local dtstr = expand_date(str)
+ if dtstr ~= nil
+ then
+ return ("d:" .. dtstr)
+ end
+
+ return nil
+end
+
+-- expansion of a date expression
+function expand_date(str)
+ -- simple date patterns
+ if string.find(str, "^19%d%d%-%d%d")
+ or string.find(str, "^20%d%d%-%d%d")
+ then
+ return (str)
+ end
+
+ -- "now"
+ if str == "now"
+ then
+ local t = os.time(os.date('!*t'))
+ return os.date("%FT%T", t)
+ end
+
+ -- today don't uses the time
+ if str == "today"
+ then
+ local t = os.time(os.date('!*t'))
+ return os.date("%F", t)
+ end
+
-- "yesterday", the source of all hangovers
if str == "yesterday"
then
local t = os.time(os.date('!*t'))
- return os.date("d:%F", t - 86400)
+ return os.date("%F", t - 86400)
end
-- "CVS style" relative dates such as "3 weeks ago"
@@ -467,9 +493,14 @@
if trans[type] ~= nil
then
local t = os.time(os.date('!*t'))
- return os.date("d:%F", t - (n * trans[type]))
+ if trans[type] <= 3600
+ then
+ return os.date("%FT%T", t - (n * trans[type]))
+ else
+ return os.date("%F", t - (n * trans[type]))
+ end
end
-
+
return nil
end
--- tests/t_selector_later_earlier.at
+++ tests/t_selector_later_earlier.at
@@ -0,0 +1,103 @@
+AT_SETUP([check later and earlier selectors])
+MONOTONE_SETUP
+
+ADD_FILE(testfile, [this is just a file
+])
+AT_CHECK(cp testfile testfile1)
+AT_CHECK(MONOTONE commit --date="2005-03-11T20:33:01" --branch=foo
--message=march, [], [ignore], [ignore])
+AT_CHECK(echo "`BASE_REVISION`" , [], [stdout], [ignore])
+AT_CHECK(mv stdout first)
+
+AT_DATA(testfile, [Now, this is a different file
+])
+AT_CHECK(cp testfile testfile2)
+AT_CHECK(MONOTONE commit --date="2005-04-22T12:15:00" --branch=foo
--message=aprila, [], [ignore], [ignore])
+AT_CHECK(echo "`BASE_REVISION`" , [], [stdout], [ignore])
+AT_CHECK(mv stdout second)
+
+AT_DATA(testfile, [And we change it a third time
+])
+AT_CHECK(cp testfile testfile3)
+AT_CHECK(MONOTONE commit --date="2005-04-24T07:44:39" --branch=foo
--message=aprilb, [], [ignore], [ignore])
+AT_CHECK(echo "`BASE_REVISION`" , [], [stdout], [ignore])
+AT_CHECK(mv stdout third)
+
+# -------------------
+# check 'earlier or equal' selector
+# -------------------
+
+# this time is just 'before' the first commit, thus no output should come
+AT_CHECK(RAW_MONOTONE automate select "e:2005-03-11T20:33:00", [], [stdout],
[ignore])
+AT_CHECK(mv stdout nosel)
+AT_CHECK(test 0 -eq "`wc -l <nosel`")
+
+# these sels should extract only the first commit
+# Note: the second sel is the exact time of the first commit.
+AT_CHECK(cp -f first expout)
+AT_CHECK(RAW_MONOTONE automate select "e:2005-04", [], [expout], [ignore])
+AT_CHECK(RAW_MONOTONE automate select "e:2005-03-11T20:33:01", [], [expout],
[ignore])
+AT_CHECK(RAW_MONOTONE automate select "e:2005-03-11T20:33:02", [], [expout],
[ignore])
+
+# now the first two
+AT_CHECK(cat second first , [], [stdout], [ignore])
+AT_CHECK(mv stdout expout)
+AT_CHECK(RAW_MONOTONE automate select "e:2005-04-23", [], [expout], [ignore])
+
+# finally, all the files
+AT_CHECK(RAW_MONOTONE automate select "e:2005-04-30", [], [stdout], [ignore])
+AT_CHECK(mv stdout a_s)
+AT_CHECK(test 3 -eq "`wc -l <a_s`")
+AT_CHECK(RAW_MONOTONE automate select "e:2006-07", [], [stdout], [ignore])
+AT_CHECK(mv stdout a_s)
+AT_CHECK(test 3 -eq "`wc -l <a_s`")
+
+# -------------------
+# check 'later' selector
+# -------------------
+
+# unlike 'earlier', the 'later' selector matches only strictly greater
+# commit times. Giving a time equal to that of third commit thus
+# should not match anything
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-24T07:44:39", [], [stdout],
[ignore])
+AT_CHECK(mv stdout nosel)
+AT_CHECK(test 0 -eq "`wc -l <nosel`")
+AT_CHECK(RAW_MONOTONE automate select "l:2005-05", [], [stdout], [ignore])
+AT_CHECK(mv stdout nosel)
+AT_CHECK(test 0 -eq "`wc -l <nosel`")
+
+# these sels should extract only the last commit
+# Note: the second sel is one sec before the last commit
+AT_CHECK(cp -f third expout)
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-23", [], [expout], [ignore])
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-24T07:44:38", [], [expout],
[ignore])
+
+# now we match the second and third commit
+AT_CHECK(cat third second, [], [stdout], [ignore])
+AT_CHECK(mv stdout expout)
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-21", [], [expout], [ignore])
+
+# finally, all the files
+AT_CHECK(RAW_MONOTONE automate select "l:2005-03", [], [stdout], [ignore])
+AT_CHECK(mv stdout a_s)
+AT_CHECK(test 3 -eq "`wc -l <a_s`")
+AT_CHECK(RAW_MONOTONE automate select "l:2003-01", [], [stdout], [ignore])
+AT_CHECK(mv stdout a_s)
+AT_CHECK(test 3 -eq "`wc -l <a_s`")
+
+# -------------------
+# check combined selectors
+# -------------------
+
+# matching only the second commit
+AT_CHECK(cp -f second expout)
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-01/e:2005-04-23", [],
[expout], [ignore])
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-01/e:2005-04-22T20:00:00",
[], [expout], [ignore])
+AT_CHECK(RAW_MONOTONE automate select
"l:2005-04-21T23:01:00/e:2005-04-22T20:00:00", [], [expout], [ignore])
+
+# non overlapping intervals should not match, even if the single selector
+# will
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-22/e:2005-04-21", [],
[stdout], [ignore])
+AT_CHECK(mv stdout nosel)
+AT_CHECK(test 0 -eq "`wc -l <nosel`")
+
+AT_CLEANUP
--- testsuite.at
+++ testsuite.at
@@ -636,3 +636,4 @@
m4_include(tests/t_drop_vs_patch_rename.at)
m4_include(tests/t_unreadable_MT.at)
m4_include(tests/t_cvsimport3.at)
+m4_include(tests/t_selector_later_earlier.at)
#
# add_file "tests/t_selector_later_earlier.at"
#
# patch "database.cc"
# from [8bd9d2d49322d496a8cd4ed3e3a8f802eb0d9ed2]
# to [243991e12528efdca0fcee02fa37275a2df3b7f6]
#
# patch "lua.cc"
# from [51659bca46465ce818dc3ed0adf1a81568c64cf3]
# to [76cfc26a5a3d0c64a9f244d78ad51c8975aa8d52]
#
# patch "lua.hh"
# from [5840b51a224c1bf2e25d448b00c2a3c48e2fd1dc]
# to [a1b3877535049b77b678d864354d030f8433854c]
#
# patch "monotone.texi"
# from [1043aaa99cad1d0060f75058661a71ad85c12ac5]
# to [8a3ea89c980dfe06b6323d42eb27a388908cec28]
#
# patch "selectors.cc"
# from [e0fe016aca3d1fcc1c64d3f7543b4e2bc7419e64]
# to [9bffb57576eaaf2b915f3ca01182b9c9d38b26f7]
#
# patch "selectors.hh"
# from [0a31a5fa5e4adaf3e65f8c0935fb58e88cee0aac]
# to [5395660518db493e48ec71989769e70515e8697e]
#
# patch "std_hooks.lua"
# from [4ed8fde74ed25fbbc5e693d3acd81c468768fed0]
# to [e546fd6e63e0bcf7b31fe90cedf3fe85e2a3d534]
#
# patch "tests/t_selector_later_earlier.at"
# from []
# to [fafef1139ab46496ae3b55a18ede48f2afa6baab]
#
# patch "testsuite.at"
# from [6b0d1c1e97cbb6def87352d063db38857a432f48]
# to [9c4c0555965abbeb8d070da8429bca44050cb2d8]
#
--- database.cc
+++ database.cc
@@ -154,6 +154,19 @@
sqlite3_result_blob(f, decoded().c_str(), decoded().size(),
SQLITE_TRANSIENT);
}
+static void
+sqlite3_unbase64_text_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
+{
+ if (nargs != 1)
+ {
+ sqlite3_result_error(f, "need exactly 1 arg to unbase64_text()", -1);
+ return;
+ }
+ data decoded;
+ decode_base64(base64<data>(string(sqlite3_value_text_s(args[0]))), decoded);
+ sqlite3_result_text(f, decoded().c_str(), decoded().size(),
SQLITE_TRANSIENT);
+}
+
static void
sqlite3_unpack_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
{
@@ -1754,6 +1767,11 @@
SQLITE_UTF8, NULL,
&sqlite3_unpack_fn,
NULL, NULL) == 0);
+
+ I(sqlite3_create_function(sql(), "unbase64_text", 1,
+ SQLITE_UTF8, NULL,
+ &sqlite3_unbase64_text_fn,
+ NULL, NULL) == 0);
}
void
@@ -2151,6 +2169,8 @@
s = branch_cert_name;
break;
case selectors::sel_date:
+ case selectors::sel_later:
+ case selectors::sel_earlier:
s = date_cert_name;
break;
case selectors::sel_tag:
@@ -2198,7 +2218,7 @@
lim += (F("WHERE id GLOB '%s*'")
% i->second).str();
}
- else if (i->first == selectors::sel_cert)
+ else if (i->first == selectors::sel_cert)
{
if (i->second.length() > 0)
{
@@ -2239,9 +2259,19 @@
{
string certname;
selector_to_certname(i->first, certname);
- lim += "SELECT id FROM revision_certs ";
- lim += (F("WHERE name='%s' AND unbase64(value) glob '*%s*'")
- % certname % i->second).str();
+ lim += (F("SELECT id FROM revision_certs WHERE name='%s' AND ")
% certname).str();
+ switch (i->first)
+ {
+ case selectors::sel_earlier:
+ lim += (F("unbase64_text(value) <= '%s'") % i->second).str();
+ break;
+ case selectors::sel_later:
+ lim += (F("unbase64_text(value) > '%s'") % i->second).str();
+ break;
+ default:
+ lim += (F("unbase64_text(value) glob '*%s*'") %
i->second).str();
+ break;
+ }
}
}
}
@@ -2279,6 +2309,8 @@
query += (F(" AND (id IN %s)") % lim).str();
}
+ //std::cerr << query << std::endl; // debug expr
+
results res;
fetch(res, one_col, any_rows, query.c_str());
for (size_t i = 0; i < res.size(); ++i)
@@ -2444,4 +2476,3 @@
{
committed = true;
}
-
--- lua.cc
+++ lua.cc
@@ -648,6 +648,20 @@
}
bool
+lua_hooks::hook_expand_date(std::string const & sel,
+ std::string & exp)
+{
+ exp.clear();
+ bool res= Lua(st)
+ .func("expand_date")
+ .push_str(sel)
+ .call(1,1)
+ .extract_str(exp)
+ .ok();
+ return res && exp.size();
+}
+
+bool
lua_hooks::hook_get_branch_key(cert_value const & branchname,
rsa_keypair_id & k)
{
--- lua.hh
+++ lua.hh
@@ -39,6 +39,7 @@
// cert hooks
bool hook_expand_selector(std::string const & sel, std::string & exp);
+ bool hook_expand_date(std::string const & sel, std::string & exp);
bool hook_get_branch_key(cert_value const & branchname, rsa_keypair_id & k);
bool lua_hooks::hook_get_priv_key(rsa_keypair_id const & k,
base64< arc4<rsa_priv_key> > & priv_key );
--- monotone.texi
+++ monotone.texi
@@ -2292,7 +2292,20 @@
@code{branch} certs where the cert value begins with @code{net.venge}.
@item Date selection
Uses selector type @code{d}. For example, @code{d:2004-04} matches
address@hidden certs where the cert value begins with @code{2004-04}.
address@hidden certs where the cert value begins with
address@hidden This selector also accepts expanded date syntax (see below).
address@hidden "Earlier or equal than" selection
+Uses selector type @code{e}. For example, @code{e:2004-04-25} matches
address@hidden certs where the cert value is less or equal than
address@hidden:00:00}. If the time component is unspecified,
+monotone will assume 00:00:00. This selector also accepts expanded date
+syntax (see below)
address@hidden "Later than" selection
+Uses selector type @code{l}. For example, @code{l:2004-04-25} matches
address@hidden certs where the cert value is strictly less than
address@hidden:00:00}. If the time component is unspecified,
+monotone will assume 00:00:00. This selector also accepts expanded date
+syntax (see below)
@item Identifier selection
Uses selector type @code{i}. For example, @code{i:0f3a} matches
revision IDs which begin with @code{0f3a}.
@@ -2325,6 +2338,37 @@
addresses, branch names, or date specifications. For the complete
source code of the hook, see @ref{Hook Reference}.
address@hidden Expanding dates
+
+All date-related selectors (@code{d}, @code{e}, @code{l}) support an
+english-like syntax similar to CVS. This syntax is expanded to the
+numeric format by a lua hook: @code{expand_date}.
+The allowed date formats are:
address@hidden @asis
+
address@hidden now
+Expands to the current date and time.
address@hidden today
+Expands to today's date. @code{e} and @code{l} selectors assume time 00:00:00
address@hidden yesterday
+Expands to yesterday's date. @code{e} and @code{l} selectors assume
+time 00:00:00
address@hidden <number> @{minute|address@hidden <ago>
+Expands to today date and time, minus the specified @code{number} of
+minutes|hours.
address@hidden <number> @{day|week|month|address@hidden <ago>
+Expands to today date, minus the specified @code{number} of
+days|weeks|months|years. @code{e} and @code{l} selectors assume time
+00:00:00
address@hidden <year>-<month>[-day[Thour:minute:second]]
+Expands to the supplied year/month. The day and time component are
+optional. If missing, @code{e} and @code{l} selectors assume the first
+day of month and time 00:00:00.
+The time component, if supplied, must be complete to the second.
address@hidden table
+
+For the complete source code of the hook, see @ref{Hook Reference}.
+
@heading Typeless selection
If, after expansion, a selector still has no type, it is matched as a
@@ -5356,11 +5400,10 @@
@group
function expand_selector(str)
- -- simple date patterns
- if string.find(str, "^19%d%d%-%d%d")
- or string.find(str, "^20%d%d%-%d%d")
+ -- something which looks like a generic cert pattern
+ if string.find(str, "^[^=]*=.*$")
then
- return ("d:" .. str)
+ return ("c:" .. str)
end
-- something which looks like an email address
@@ -5381,11 +5424,55 @@
return ("i:" .. str)
end
+ -- tries to expand as a date
+ local dtstr = expand_date(str)
+ if dtstr ~= nil
+ then
+ return ("d:" .. dtstr)
+ end
+
+ return nil
+end
address@hidden group
address@hidden smallexample
+
address@hidden expand_date (@var{str})
+
+Attempts to expand @var{str} as a date expression. Expansion means recognizing
+and interpreting special words such as @code{yesterday} or @code{6
+months ago} and converting them into well formed date expressions. For more
+detail on the use of selectors, see @ref{Selectors}. The default
+definition of this hook is:
+
address@hidden
address@hidden
+function expand_date(str)
+ -- simple date patterns
+ if string.find(str, "^19%d%d%-%d%d")
+ or string.find(str, "^20%d%d%-%d%d")
+ then
+ return (str)
+ end
+
+ -- "now"
+ if str == "now"
+ then
+ local t = os.time(os.date('!*t'))
+ return os.date("%FT%T", t)
+ end
+
+ -- today don't uses the time
+ if str == "today"
+ then
+ local t = os.time(os.date('!*t'))
+ return os.date("%F", t)
+ end
+
-- "yesterday", the source of all hangovers
if str == "yesterday"
then
local t = os.time(os.date('!*t'))
- return os.date("d:%F", t - 86400)
+ return os.date("%F", t - 86400)
end
-- "CVS style" relative dates such as "3 weeks ago"
@@ -5397,21 +5484,24 @@
month = 2678400;
year = 31536000
@}
- local pos, len, n, type = string.find(str, "(%d+)
- ([minutehordaywk]+)s? ago")
+ local pos, len, n, type = string.find(str, "(%d+) ([minutehordaywk]+)s?
ago")
if trans[type] ~= nil
then
local t = os.time(os.date('!*t'))
- return os.date("d:%F", t - (n * trans[type]))
+ if trans[type] <= 3600
+ then
+ return os.date("%FT%T", t - (n * trans[type]))
+ else
+ return os.date("%F", t - (n * trans[type]))
+ end
end
-
+
return nil
end
@end group
@end smallexample
-
@item get_system_linesep ()
Returns a string which defines the default system line separator.
--- selectors.cc
+++ selectors.cc
@@ -22,9 +22,9 @@
L(F("decoding selector '%s'\n") % sel);
+ std::string tmp;
if (sel.size() < 2 || sel[1] != ':')
{
- std::string tmp;
if (!app.lua.hook_expand_selector(sel, tmp))
{
L(F("expansion of selector '%s' failed\n") % sel);
@@ -58,11 +58,35 @@
case 'c':
type = sel_cert;
break;
+ case 'l':
+ type = sel_later;
+ break;
+ case 'e':
+ type = sel_earlier;
+ break;
default:
W(F("unknown selector type: %c\n") % sel[0]);
break;
}
sel.erase(0,2);
+
+ /* a selector date-related should be validated */
+ if (sel_date==type || sel_later==type || sel_earlier==type)
+ {
+ N (app.lua.hook_expand_date(sel, tmp),
+ F ("selector '%s' is not a valid date\n") % sel);
+
+ if (tmp.size()<8 && (sel_later==type || sel_earlier==type))
+ tmp += "-01T00:00:00";
+ else if (tmp.size()<11 && (sel_later==type || sel_earlier==type))
+ tmp += "T00:00:00";
+ N(tmp.size()==19 || sel_date==type, F ("selector '%s' is not a
valid date (%s)\n") % sel % tmp);
+ if (sel != tmp)
+ {
+ P (F ("expanded date '%s' -> '%s'\n") % sel % tmp);
+ sel = tmp;
+ }
+ }
}
}
--- selectors.hh
+++ selectors.hh
@@ -25,6 +25,8 @@
sel_tag,
sel_ident,
sel_cert,
+ sel_earlier,
+ sel_later,
sel_unknown
}
selector_type;
--- std_hooks.lua
+++ std_hooks.lua
@@ -422,13 +422,6 @@
return ("c:" .. str)
end
- -- simple date patterns
- if string.find(str, "^19%d%d%-%d%d")
- or string.find(str, "^20%d%d%-%d%d")
- then
- return ("d:" .. str)
- end
-
-- something which looks like an email address
if string.find(str, "address@hidden")
then
@@ -447,11 +440,44 @@
return ("i:" .. str)
end
+ -- tries to expand as a date
+ local dtstr = expand_date(str)
+ if dtstr ~= nil
+ then
+ return ("d:" .. dtstr)
+ end
+
+ return nil
+end
+
+-- expansion of a date expression
+function expand_date(str)
+ -- simple date patterns
+ if string.find(str, "^19%d%d%-%d%d")
+ or string.find(str, "^20%d%d%-%d%d")
+ then
+ return (str)
+ end
+
+ -- "now"
+ if str == "now"
+ then
+ local t = os.time(os.date('!*t'))
+ return os.date("%FT%T", t)
+ end
+
+ -- today don't uses the time
+ if str == "today"
+ then
+ local t = os.time(os.date('!*t'))
+ return os.date("%F", t)
+ end
+
-- "yesterday", the source of all hangovers
if str == "yesterday"
then
local t = os.time(os.date('!*t'))
- return os.date("d:%F", t - 86400)
+ return os.date("%F", t - 86400)
end
-- "CVS style" relative dates such as "3 weeks ago"
@@ -467,9 +493,14 @@
if trans[type] ~= nil
then
local t = os.time(os.date('!*t'))
- return os.date("d:%F", t - (n * trans[type]))
+ if trans[type] <= 3600
+ then
+ return os.date("%FT%T", t - (n * trans[type]))
+ else
+ return os.date("%F", t - (n * trans[type]))
+ end
end
-
+
return nil
end
--- tests/t_selector_later_earlier.at
+++ tests/t_selector_later_earlier.at
@@ -0,0 +1,103 @@
+AT_SETUP([check later and earlier selectors])
+MONOTONE_SETUP
+
+ADD_FILE(testfile, [this is just a file
+])
+AT_CHECK(cp testfile testfile1)
+AT_CHECK(MONOTONE commit --date="2005-03-11T20:33:01" --branch=foo
--message=march, [], [ignore], [ignore])
+AT_CHECK(echo "`BASE_REVISION`" , [], [stdout], [ignore])
+AT_CHECK(mv stdout first)
+
+AT_DATA(testfile, [Now, this is a different file
+])
+AT_CHECK(cp testfile testfile2)
+AT_CHECK(MONOTONE commit --date="2005-04-22T12:15:00" --branch=foo
--message=aprila, [], [ignore], [ignore])
+AT_CHECK(echo "`BASE_REVISION`" , [], [stdout], [ignore])
+AT_CHECK(mv stdout second)
+
+AT_DATA(testfile, [And we change it a third time
+])
+AT_CHECK(cp testfile testfile3)
+AT_CHECK(MONOTONE commit --date="2005-04-24T07:44:39" --branch=foo
--message=aprilb, [], [ignore], [ignore])
+AT_CHECK(echo "`BASE_REVISION`" , [], [stdout], [ignore])
+AT_CHECK(mv stdout third)
+
+# -------------------
+# check 'earlier or equal' selector
+# -------------------
+
+# this time is just 'before' the first commit, thus no output should come
+AT_CHECK(RAW_MONOTONE automate select "e:2005-03-11T20:33:00", [], [stdout],
[ignore])
+AT_CHECK(mv stdout nosel)
+AT_CHECK(test 0 -eq "`wc -l <nosel`")
+
+# these sels should extract only the first commit
+# Note: the second sel is the exact time of the first commit.
+AT_CHECK(cp -f first expout)
+AT_CHECK(RAW_MONOTONE automate select "e:2005-04", [], [expout], [ignore])
+AT_CHECK(RAW_MONOTONE automate select "e:2005-03-11T20:33:01", [], [expout],
[ignore])
+AT_CHECK(RAW_MONOTONE automate select "e:2005-03-11T20:33:02", [], [expout],
[ignore])
+
+# now the first two
+AT_CHECK(cat second first , [], [stdout], [ignore])
+AT_CHECK(mv stdout expout)
+AT_CHECK(RAW_MONOTONE automate select "e:2005-04-23", [], [expout], [ignore])
+
+# finally, all the files
+AT_CHECK(RAW_MONOTONE automate select "e:2005-04-30", [], [stdout], [ignore])
+AT_CHECK(mv stdout a_s)
+AT_CHECK(test 3 -eq "`wc -l <a_s`")
+AT_CHECK(RAW_MONOTONE automate select "e:2006-07", [], [stdout], [ignore])
+AT_CHECK(mv stdout a_s)
+AT_CHECK(test 3 -eq "`wc -l <a_s`")
+
+# -------------------
+# check 'later' selector
+# -------------------
+
+# unlike 'earlier', the 'later' selector matches only strictly greater
+# commit times. Giving a time equal to that of third commit thus
+# should not match anything
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-24T07:44:39", [], [stdout],
[ignore])
+AT_CHECK(mv stdout nosel)
+AT_CHECK(test 0 -eq "`wc -l <nosel`")
+AT_CHECK(RAW_MONOTONE automate select "l:2005-05", [], [stdout], [ignore])
+AT_CHECK(mv stdout nosel)
+AT_CHECK(test 0 -eq "`wc -l <nosel`")
+
+# these sels should extract only the last commit
+# Note: the second sel is one sec before the last commit
+AT_CHECK(cp -f third expout)
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-23", [], [expout], [ignore])
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-24T07:44:38", [], [expout],
[ignore])
+
+# now we match the second and third commit
+AT_CHECK(cat third second, [], [stdout], [ignore])
+AT_CHECK(mv stdout expout)
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-21", [], [expout], [ignore])
+
+# finally, all the files
+AT_CHECK(RAW_MONOTONE automate select "l:2005-03", [], [stdout], [ignore])
+AT_CHECK(mv stdout a_s)
+AT_CHECK(test 3 -eq "`wc -l <a_s`")
+AT_CHECK(RAW_MONOTONE automate select "l:2003-01", [], [stdout], [ignore])
+AT_CHECK(mv stdout a_s)
+AT_CHECK(test 3 -eq "`wc -l <a_s`")
+
+# -------------------
+# check combined selectors
+# -------------------
+
+# matching only the second commit
+AT_CHECK(cp -f second expout)
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-01/e:2005-04-23", [],
[expout], [ignore])
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-01/e:2005-04-22T20:00:00",
[], [expout], [ignore])
+AT_CHECK(RAW_MONOTONE automate select
"l:2005-04-21T23:01:00/e:2005-04-22T20:00:00", [], [expout], [ignore])
+
+# non overlapping intervals should not match, even if the single selector
+# will
+AT_CHECK(RAW_MONOTONE automate select "l:2005-04-22/e:2005-04-21", [],
[stdout], [ignore])
+AT_CHECK(mv stdout nosel)
+AT_CHECK(test 0 -eq "`wc -l <nosel`")
+
+AT_CLEANUP
--- testsuite.at
+++ testsuite.at
@@ -636,3 +636,4 @@
m4_include(tests/t_drop_vs_patch_rename.at)
m4_include(tests/t_unreadable_MT.at)
m4_include(tests/t_cvsimport3.at)
+m4_include(tests/t_selector_later_earlier.at)