--- toast	2005/11/13 02:06:37	1.427
+++ toast	2005/11/16 05:54:00	1.428
@@ -855,9 +855,9 @@
   return !defined($msg);
 }
 
-sub optcdrun($@)
+sub optcdrunimpl($$@)
 {
-  my($dir, @prog) = @_;
+  my($asroot, $dir, @prog) = @_;
   announce("(cd $dir; @prog)");
   my($pid);
   if($pid = fork) # parent
@@ -868,17 +868,30 @@
   else # child
   {
     defined($pid) || error("fork: $!");
+    dropprivs() unless $asroot;
     silentcd($dir);
     safeexec(@prog);
   }
 }
 
+sub optcdrun($@)
+{
+  my($dir, @prog) = @_;
+  optcdrunimpl(true, $dir, @prog);
+}
+
 sub cdrun($@)
 {
   my($dir, @prog) = @_;
   optcdrun($dir, @prog) || error("@prog returned $?");
 }
 
+sub cdrunnonroot($@)
+{
+  my($dir, @prog) = @_;
+  optcdrunimpl(false, $dir, @prog) || error("@prog returned $?");
+}
+
 sub shellescape(@)
 {
   my(@words) = @_;
@@ -1363,6 +1376,7 @@
 sub guessnv(@)
 {
   my(@urls) = @_;
+  s/^(cvs.*)\#(\d+)$/$1-$2/ for @urls;
   my($base) = collapse(map(stripext(basename(stripquery($_))), @urls));
   $base =~ /^([\w]+[\w\-]*[a-z]+)[-_]v?(\d[\w\.\+\-]+)$/i  # Cryptix_src_3-1-1
       || $base =~ /^([a-z][a-z_-]*[a-z])\.(\d[\d\.]+)$/i #device-mapper.1.00.07
@@ -2518,6 +2532,21 @@
 {
   my($url, $dir) = @_;
 
+  if($url =~ m!^cvsroot(\+ssh)?:(.+)/([^/]+)\#(\d+)$!)
+  {
+    my($usessh, $cvsroot, $module, $time) = ($1, $2, $3, $4);
+    announce("export CVS_RSH=ssh") if $usessh;
+    local($ENV{CVS_RSH}) = "ssh" if $usessh;
+    my($moddir) = path($dir, $module);
+    optmd($moddir);
+    chownnonroot($moddir);
+    cdrunnonroot($dir, "cvs", (quiet ? "-Q" : "-q"),
+        "-d", $cvsroot, "co", "-D", "\@$time", $module);
+    cdrun($dir, qw(tar czf), "$module.tar.gz", $module);
+    rmall($moddir);
+    return $url;
+  }
+
   my(%visited);
   for(1..5)
   {
@@ -4629,6 +4658,14 @@
   my($hasver) = false;
   for(@urls)
   {
+    if(/^(cvs.*)\#\Q$version\E$/)
+    {
+      my($newurl) = cleanurl($1);
+      my(undef, $newv) = guessnv($newurl);
+      push(@newurls, $newurl);
+      $candidates{$newv} = $hasver = true;
+      next;
+    }
     m!^((http|ftp|file)://[^\?]+/)([^\?/]*)(\?.*)?$!i ||
         error("bad URL for upgrade: $_");
     my($dirname, $basename, $query) = undeftoempty($1, $3, $4);
@@ -5329,6 +5366,7 @@
   $url =~ s/[^\!-\~]/urlescapechar($&)/ge;
   $url .= "/" if $url =~ m!^\w+://[^/]+$!;
   $url =~ s!^(ftp://[^/:]+):21/!$1/!; # wget emits redundant ftp port no.
+  $url .= "#" . time() if $url =~ /^cvs/ && $url !~ /\#\d+$/;
   return $url;
 }
 
@@ -5435,7 +5473,7 @@
         }
 
         my($url);
-        if(/^\w+:./)
+        if(/^[\w\+]+:./)
         {
           $url = cleanurl($_);
           if(expand && $url =~ m!/$!)
@@ -6518,6 +6556,9 @@
 built-in routines.  B<toast get> always handles C<file> URLs itself.
 If B<ssh> is available, B<toast get> can use it to fetch (non-standard)
 URLs of the form C<ssh://[username@]hostname/absolute/path/to/file>.
+If B<cvs> is available, B<toast get> can also handle (even less standard)
+URLs of the form C<cvsroot[+ssh]:$CVSROOT/module[#unixtime]>; note that
+$CVSROOT often starts with a colon, resulting in two colons in a row.
 
 =item S<B<toast build> [ I<PACKAGE> ...]>
 
@@ -7424,6 +7465,7 @@
   - "toast get" prevents wget from truncating output if transfer restarts
   - "toast env" doesn't set PYTHONPATH
   - "toast build linux" may fail during install phase w/o --nopreload
+  - "toast add http://x/foo --confappend=bar" fails
 
 Wish list: