monotone-devel
[Top][All Lists]
Advanced

[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)


reply via email to

[Prev in Thread] Current Thread [Next in Thread]