--- toast	2003/08/26 23:21:16	1.185
+++ toast	2003/08/30 06:47:16	1.186
@@ -160,6 +160,7 @@
     "postarmprog" => superuser ? "/sbin/ldconfig" : "",
     "verbose" => true,
     "autofind" => true,
+    "autorename" => true,
     "autoclean" => true,
     "autopurge" => false,
     "autoarm" => true,
@@ -1483,7 +1484,7 @@
   return &smartgeturl($redir, $dir, $ttl - 1);
 }
 
-sub autorename($$)
+sub autorenamepkg($$)
 {
   my($name, $version) = @_;
 
@@ -1515,7 +1516,7 @@
 sub get(@)
 {
   my($name, $version, $build, @urls) = @_;
-  my($autorename) = !defined($version);
+  my($autorename) = !defined($version) && autorename;
 
   ($name, $version) = add(@_) if @urls || !isadded($name, $version);
 
@@ -1532,7 +1533,7 @@
   optmd($tempdir);
   smartgeturl($_, $tempdir) foreach @urls;
   mv($tempdir, $realdir);
-  ($name, $version) = autorename($name, $version) if $autorename;
+  ($name, $version) = autorenamepkg($name, $version) if $autorename;
 
   ($name, $version);
 }
@@ -2684,6 +2685,115 @@
 
 ##############################################################################
 
+sub upgrade(@)
+{
+  my($name, $version, $build, @urls) = @_;
+  error unless defined($name);
+  error unless defined($version);
+  error if defined($build);
+
+  @urls = pkgurls($name, $version) unless @urls;
+  error unless @urls;
+
+  my($pkgname) = pkgname($name, $version);
+
+  my(%linkmap);
+  my($verpat) = $version =~ /^\d/ ? '\d.*' : '.+';
+
+  my(%candidates);
+  my(@newurls);
+  for(@urls)
+  {
+    m!^((http|ftp)://[^\?]+/)([^\?/]*)(\?.*)?$!i ||
+        error("bad URL for upgrade: $_");
+    my($dirname, $basename, $query) = undeftoempty($1, $3, $4);
+    if($basename !~ /^(.*)\Q$version\E(.*)$/)
+    {
+      push(@newurls, $_); # URL has no version number; use as-is
+    }
+    else
+    {
+      my($pre, $post) = ($1, $2);
+      $linkmap{$dirname} = [linksfromurl($dirname)]
+          unless exists($linkmap{$dirname});
+      my(@links) = @{$linkmap{$dirname}};
+      my(%vermap);
+      for(@links)
+      {
+        if(/^\Q$dirname$pre\E($verpat)\Q$post\E(\?.*)?$/)
+        {
+          $vermap{$1} = $_;
+          $candidates{$1} = 1;
+        }
+      }
+      push(@newurls, \%vermap);
+    }
+  }
+
+  my($newver);
+  for(reverse(sort cmpab keys(%candidates)))
+  {
+    my($candidate) = $_;
+    my($ok) = true;
+    for(@newurls)
+    {
+      next unless ref;
+      my(%vermap) = %$_;
+      if(!exists($vermap{$candidate}))
+      {
+        $ok = false;
+        last;
+      }
+    }
+    if($ok)
+    {
+      $newver = $candidate;
+      last;
+    }
+  }
+
+  error("URLs for $pkgname all seem version-neutral and " .
+      "autorename is off; aborting") if !defined($version) && !autorename;
+
+  for(@newurls)
+  {
+    next unless ref;
+    error("can't find alternate version for $pkgname") unless defined($newver);
+    my(%vermap) = %$_;
+    $_ = $vermap{$newver};
+  }
+
+  if(defined($newver))
+  {
+    error("$pkgname appears to be the latest available version")
+        if $version eq $newver;
+    my(@vers) = sort cmpab ($version, $newver);
+    error("only found older versions of $pkgname") if $vers[1] eq $version;
+  }
+
+  my(@cmdargs) = ($name, $newver, undef, @newurls);
+  if(isarmed($name, $version) && autoarm)
+  {
+    return arm(@cmdargs);
+  }
+  elsif(isbuilt($name, $version))
+  {
+    return build(@cmdargs);
+  }
+  elsif(isstored($name, $version))
+  {
+    return get(@cmdargs);
+  }
+  else # not eligible for autorename
+  {
+    error("URLs for unstored package $pkgname seem version-neutral; aborting")
+        unless defined($newver);
+    return add(@cmdargs);
+  }
+}
+
+##############################################################################
+
 sub ensuredisarmed($;$$)
 {
   my($name, $version, $build) = @_;
@@ -3299,6 +3409,7 @@
 sub parse_build(@) { allowempty(uselatestversion(rejectbuilds(parse(@_)))); }
 sub parse_clean(@) { allowempty(rejectmissing(parse(@_))); }
 sub parse_arm(@) { rejectempty(uselatestbuild(parse(@_))); }
+sub parse_upgrade(@) { rejectempty(rejectmissing(uselatestversion(rejectbuilds(parse(@_))))); }
 sub parse_disarm(@) { rejectempty(rejectmissing(parse(@_))); }
 sub parse_demolish(@) { rejectempty(rejectmissing(parse(@_))); }
 sub parse_purge(@) { rejectempty(rejectmissing(rejectbuilds(parse(@_)))); }
@@ -3675,6 +3786,21 @@
 (if any).  If the latest build is already armed, the command fails; you
 probably meant to invoke B<toast build> with the C<autoarm> option set.
 
+=item S<B<toast upgrade> I<PACKAGE> ...>
+
+Checks for a later version of an existing package.  The existing package's
+URLs are used as a starting point to locate the new version.  If the
+filename component of a given URL doesn't appear to contain the package's
+version number, that URL will be left unmodified for the new version;
+otherwise, the directory portion of the URL will be immediately downloaded
+and searched for a similar URL containing a higher version number.
+The command fails if a single newer version for all version-containing
+URLs cannot be found; otherwise, the highest eligible version is used
+for all modified URLs and the package itself.  The command performs an
+implicit <add>, B<get>, B<build> or B<arm> on the extrapolated URLs so
+as to match the state of the given existing version, except that the
+new package will never be armed if the B<autoarm> option is disabled.
+
 =item S<B<toast disarm> I<BUILD> | I<PACKAGE> ...>
 
 Deletes symlinks created by B<toast arm>.  This works by removing symbolic
@@ -3900,6 +4026,20 @@
 been added previously or given explicitly.  If no version number is
 given either, the latest version listed on freshmeat.net will be used.
 Default: enabled.
+
+=item S<B<--autorename> | B<--noautorename>>
+
+When B<autorename> is enabled, B<toast get> may try to use the contents of
+the files it downloads to attempt to guess a new name for any implicitly
+added package for which no name and/or version number was specified on
+the command line or could be guessed from the URLs given.  If this method
+results in a new name being guessed, the package is renamed automatically
+as if by B<toast rename>, and any further processing continues under
+the new name.  If B<autorename> is disabled, packages with unguessed
+or partially guessed names always keep the unique names automatically
+assigned by B<toast add> based on URLs alone (version number will be
+C<unknown> optionally followed by a serial number for uniqueness; name
+may have been guessed or may also be C<unknown>).  Default: enabled.
 
 =item S<B<--autoclean> | B<--noautoclean>>