#!/usr/bin/perl
#
# urpmc - user rpm change(s|log) i.e. outputs a list of packages to be updated
# from a urpmi medium and the changes made to them.
#
# $Id: urpmc,v 1.2 2003/07/16 04:48:45 breser Exp $
#
# copyright (c) 2003  Ben Reser <ben@reser.org>
#
# ---------------------------------------------------------------------
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

###############################################################################
# Configuration options
###############################################################################
 
# Path to your urpmi.update program.
my $URPMI_UPDATE_PATH = '/usr/sbin/urpmi.update';

# Path to your urpmq program.
my $URPMQ_PATH = '/usr/bin/urpmq';

###############################################################################
# DO NOT MODIFY BELOW HERE UNLESS YOU KNOW WHAT YOU ARE DOING!
###############################################################################

use strict;
use warnings;
use sigtrap qw(die error-signals HUP INT TERM);
require urpm; # *MUST* be require otherwise it imports _ and borks File::Temp
use URPM; # gotta love Mandrake and their confusing name, yes there is a capped and lowercase
          # version of the urpm module/lib and no they aren't the same.
use Getopt::Long;
use Pod::Usage;
use packdrake;
use POSIX qw(strftime);
use File::Temp qw(tempdir);

# command line options
my $changelog = 1;
my $changelog_regex = '^(\*.*?)\s*^\*'; # Default for 1 changelog entry
my $changelog_to_current = 1;
my $runupdate = $> == 0; # default not to run update for any user except root!
my $verbose = 0;
my $compact = 1;
my $wget = '';
my $curl = '';
my $proxy = '';
my $proxyuser = '';
my $show_current = 1;

# options for printing package names:
my $full = 0; # version, release, and arch
my $nameonly = 0; # nothing but the name :)

# global vars
my @update_media;
my $urpm;

# Spiffy stuff to allow cvs to update the version and date!
# Note the my has to be on a separate line from $VERSION so
# MakeMaker can get the VERSION from this file.
my
($VERSION) = '$Revision: 1.2 $' =~ /\$\w+: (.*?) \$/;
my ($DATE)    = '$Date: 2003/07/16 04:48:45 $' =~ /\$\w+: (.*?) \$/;

# Must open urpm *BEFORE* parsing the command line
# because we need it to do so.
parse_urpmi_config();

parse_command_line();


urpmi_update(@update_media) if ($runupdate);
if ($changelog) {
  print_changelog(urpmq(@update_media));
} else {
  print_package_list(urpmq(@update_media));
}

# define sub-routines

# parse the urpmi config so we can look up details on the media.
sub parse_urpmi_config {
  $urpm = new urpm or die q(Couldn't initialize the urpm module to read the urpmi config file);
  $urpm->read_config(nocheck_access => 1) or die q(Couldn't initialize the urpm module to read the urpmi config file);
}

# parse command-line
sub parse_command_line {
  my ($help,$man,$version) = (0,0,0);
  my ($update_flag_select) = 0;
  my (@ignore) = ();

  Getopt::Long::Configure('bundling');
  my $getopt_return = GetOptions ('updatemedia!' => \$runupdate,
                                  'um!' => \$runupdate,
                                  'updatesource!' => \$runupdate,
                                  'us!' => \$runupdate,
                                  'update' => \$update_flag_select,
                                  'changelog!' => \$changelog,
                                  'compact!' => \$compact,
                                  'help|h|?' => \$help, 'man'=>\$man,
                                  'version|V' => \$version,
                                  'wget' => sub { $wget = '--wget' },
                                  'curl' => sub { $curl = '--curl' },
                                  'proxy=s' => \$proxy,
                                  'proxyuser=s' => \$proxyuser,
                                  'nameonly' => sub { $nameonly = $_[1];
                                                      $show_current = $_[1] ? 0 : 1 },
                                  'f' => \$full,
                                  'n=i' => \&generate_changelog_regex,
                                  'showcurrent!' => sub { $show_current = $_[1];
                                                          $nameonly = $_[1] ? 0 : $nameonly },
                                  'sc!' => sub { $show_current = $_[1];
                                                          $nameonly = $_[1] ? 0 : $nameonly },
                                  'tocurrent!' => \$changelog_to_current,
                                  'tc!' => \$changelog_to_current,
                                  'ignore=s' => \@ignore,
                                  'verbose|v' => \$verbose);
 
  unless ($getopt_return) {
    header();
    pod2usage(2);
  }

  if ($help) {
    header();
    pod2usage(1);
  }

  if ($man) {
    header();
    pod2usage( -exitval => 0, -verbose => 2);
  }

  if ($version) {
    header();
    exit(0);
  }

  if ($runupdate && $> != 0) {
    die "Only root can update the media definitions."
  }
  
  if ($update_flag_select) {
    if (@ARGV) {
      header();
      pod2usage(-exitval => 2, -msg => q(You cannot specify a medium name if you use the --update flag!));
    } else {
      @update_media = get_update_media();
      unless(scalar(@update_media)) {
        header();
        pod2usage(-exitval => 2, -msg => q(You do not have any update mediums defined.  Update mediums need to be defined by passing the --update flag to uprmi.addmedia or by adding a update line to the urpmi.cfg for that medium));
      }
    }
  } elsif (@ARGV) {
    @update_media = get_hashes_from_names(@ARGV) or die qq(None of the media you passed (@ARGV) exist) ;
  } else {
    @update_media = get_all_media();
    unless(scalar(@update_media)) {
      header();
      pod2usage(-exitval => 2, -msg => q(You do not have any media defined.  Mediums need to be defined by running uprmi.addmedia or by running the Software Sources Manager.));
    }
  }

  # No media selected at all means error.
  unless (scalar(@update_media)) {
    die "No media selected for an unknown reason (i.e. this message should never appear)";
  }

  @update_media = handle_ignored_media(\@update_media,\@ignore);
 
  unless (scalar(@update_media)) {
    exit(0); # no media to check means nothing to show means die now to be fast!
  }
  # Yeah I know this is confusing.  Two almost identical checks one right after another
  # that do something different.  The key is the call to handle_ignored_media
}

# check for ignored media and remove them
# first arg is a ref to the list of media selected
# second arg is a ref to a list of command line passed media to ignore
# returns a list of the acceptable media.
sub handle_ignored_media {
  my ($media,$ignore) = @_ or die "handle_ignored_media didn't get proper input, bug";
  my (@ret);
  
  foreach my $medium (@$media) {
    if (defined($medium->{'ignore'}) && $medium->{'ignore'}) {
      next;
    }
    my $match = 0;
    foreach my $name (@$ignore) {
      if ($name eq $medium->{'name'}) {
        $match = 1;
      }
    }
    push @ret, $medium unless($match);
  }
  return @ret;
}

# returns the hashes of the urpm configs 
# based on the names of the media.
sub get_hashes_from_names {
  my (@media) = @_ or die "get_hashes_from_names didn't get proper input, bug";
  my @ret;

  foreach my $name (@media) {
    foreach my $medium (@{$urpm->{'media'}}) {
      unless (defined($medium)) {
        die "The medium entry returned by urpm doesn't appear to be valid, probably a urpm bug!";
      }
      unless (defined($medium->{'name'})) {
        die "The medium entry returned by urpm doesn't appear to have a name, probably a urpm bug!";
      }
      if ($medium->{'name'} eq $name) {
        if (defined($medium->{'synthesis'}) && $medium->{'synthesis'} &&
            $changelog)
        {
          print STDERR "$medium->{'name'} uses a synthesis file.  Cannot output changelog.\nWill list package names only.  Reconfigure the medium to use\na hdlist file to get a changelog.\n";
          $changelog = 0;
        }
        push @ret, $medium;
      }
    }
  }
  return @ret;
}
  
# Retrive the hashes of the update media
sub get_update_media {
  my @ret;
  
  foreach my $medium (@{$urpm->{'media'}}) {
    unless (defined($medium)) {
      die "The medium entry returned by urpm doesn't appear to be valid, probably a urpm bug!";
    }
    if (defined($medium->{'update'}) && $medium->{'update'}) {
      push @ret, $medium;
    }
    if (defined($medium->{'synthesis'}) && $medium->{'synthesis'} && $changelog) {
      print STDERR "$medium->{'name'} uses a synthesis file.  Cannot output changelog.\nWill list package names only.  Reconfigure the medium to use\na hdlist file to get a changelog.\n";
      $changelog = 0;
    }
  }
  return @ret;
}

# Retrieve the hashes of all the medium
sub get_all_media {
  my (@media) = @{$urpm->{'media'}};
  foreach my $medium (@media) {
    if (defined($medium->{'synthesis'}) && $medium->{'synthesis'}
        && $changelog)
    {
      print STDERR "$medium->{'name'} uses a synthesis file.  Cannot output changelog.\nWill list package names only.  Reconfigure the medium to use\na hdlist file to get a changelog.\n";
      $changelog = 0;
    }
  }
  return @media;
}

# based off the config hash build a set of
# arguments to pass to the urpm commands
# If this set of args will be passed to
# urpmi.update the first arg should be 0
# which provides a space separated list
# For most other urpm commands (urpmq)
# it should be 1.  All other args will be
# interpreted as medium hashes. 
sub build_media_args {
  my ($update) = shift;
  unless (defined($update) && ($update == 0 || $update == 1)) {
    die "build_media_args didn't get the proper first arg, should be 0 or 1, bug";
  }
  my (@media) = @_ or  die "build_media_args didn't get the media list, bug";
  my $return;

  $return = q(--media ') unless ($update);  
  foreach my $medium (@media) {
    if ($update) {
      $return .= qq('$medium->{'name'}');
      $return .= ' ';
    } else {
      $return .= $medium->{'name'};
      $return .= ',';
    }
  }
  $return .= q(') unless ($update);
  return $return;
}

# For the -n option if calculate the regex.
# This is intended to be called by Getopt::Long.
sub generate_changelog_regex {
  # 2nd ragument is the value from Getopt::Long
  unless (defined($_[1])) {
    die "generate_changelog_regex didn't get the argument, bug";
  }
  my $count = $_[1]; 

  if ($count == 0) {
    $changelog = 0; # let people use -n0 to disable changelogs
  } elsif ($count == 1) {
    $changelog = 1; # force changelogs on if -n > 0
    return; # don't do anything default is fine
  } else {
    $changelog = 1; # force changelogs on if -n > 0
    $changelog_regex =  '^(\*.*?';
    $changelog_regex .= '\s*^\*.*?' x ($count - 1);
    $changelog_regex .=  ')\s*^\*';
  }
}

# Prints the package name
# according to the prefrences
# passed on the command line.
sub print_package_name {
  my $package = shift or die "print_package_name didn't get the package name, bug";

  if ($show_current) {
    my $installed_version = find_installed_version($package);
    if (defined($installed_version)) {
      # If there isn't a version installed we don't want to try and print
      # like there is one.
      my ($package_name) = $package =~ m/(.*?)-[^-]+-[^-]+$/;
      unless (defined($package_name)) {
        die "Couldn't get base package name, bug";
      }
      print format_package_name("$package_name-$installed_version");
      print ' -> ';
    }
  }
  print format_package_name($package);
}

# formats the package name according to the prefrences
# from the command line.
sub format_package_name {
  my $package = shift or die "format_package_name didn't get proper intput bug";
  
  if ($full) {
    return $package;
  } elsif ($nameonly) {
    $package =~ s/-[^-]+-[^-]+$//;
    return $package;
  } else {
    $package =~ s/\.\w+$//;
    return $package;
  }
}

# given a list of packages print out just their names
sub print_package_list {
  my (@packages) = @_; # don't test could be no packages to update
  
  foreach my $package (sort @packages) {
    print_package_name($package);
    print "\n";
  }
}

# given a list of packages print out the changelog
# for each 
sub print_changelog {
  my (@packages) = @_; # don't test could be no packages to udpate
  
  # Make the tempdir, File::Temp will automagically clean this up for us.
  my $tempdir = tempdir('urpmc.XXXXXXXXXX',TMPDIR => 1, CLEANUP => 1) or die "Couldn't make tempdir : $!";
  
  # Extract the headers from the hdlist for each package we need.
  # We need to iterate over each medium to get them all.   Last medium 
  # has precedence which may not be how urpm(i|q) handles it.
  # XXX Test and find out to make it consistent with urpm(i|q)
  foreach my $medium (@update_media) {
    unless(defined($urpm->{'statedir'})) {
      die "statedir unknown, probable urpm bug";
    }
    unless (defined($medium) && defined($medium->{'hdlist'})) {
      die "couldn't get the path to the hdlist, probable urpm bug";
    }
    my $hdlist = "$urpm->{'statedir'}/$medium->{'hdlist'}";
    unless (-r $hdlist) {
      if ($verbose) {
        print STDERR "Cannot read the hdlist file $hdlist\nThis probably means you only have a synthesis file, skipping attempt to print changelog!\n\n";
      }
      print_package_list(@packages);
      return;
    }
    
    my $packdrake = new packdrake($hdlist,'quiet' => 1) or die "Couldn't intialize packdrake";
    $packdrake->extract_archive($tempdir,@packages); # grumble no status return for error trapping
  }
  
  # Get the changelog for each package and put it in a hash
  my %updates;
  foreach my $package (@packages) {
    my $raw_changelog = get_changelog("$tempdir/$package"); # don't test, packages could have an
                                                            # empty changelog (ICK)
    if ($changelog_to_current) {
      # Look for the changlog based upon the SRPMs version.  This is because packages
      # with multiple binary packages don't have to have the same version but
      # do all share the same changelog, so the SRPM version is a reliable way to get the
      # version that will be in the changelog
      my $srpm = find_installed_version_srpm($package);
      if (defined($srpm)) {
        my ($srpm_version) = $srpm =~ /-([^-]+-[^-]+)\.src\.rpm$/;
        if (defined($srpm_version)) {
          ($updates{$package}) = $raw_changelog =~ /^(.*?)^\*[^*]+$srpm_version/sm;
        } else {
          die "Error parsing SRPM name ($srpm), bug";
        }
      } else {
        $updates{$package} = $raw_changelog . "!!! PACKAGE NOT CURRENTLY INSTALLED FULL CHANGELOG SHOWN !!!\n";
      }
      if (!defined($updates{$package})) {
        # didn't find it, so set it to the full changelog and then add a comment
        # to the end that the changelog has likely been truncated
        $updates{$package} = $raw_changelog . "!!! CHANGELOG TRUNCATED OR CURRENT ENTRY MISSING !!!\n"
      } elsif  ($updates{$package} =~ /^\s*$/) {
        # blank changelog after match means the new version didn't have a
        # changelog.
        $updates{$package} = '!!! NO CHANGELOG ENTRIES NEWER THAN INSTALLED PACKAGE !!!';
      }
    } else {
      # separated from doing the changelog current because it provides a speed
      # increase since we don't need to recompile the pattern for each pass unless
      # we are looking for specific versions of the changelog
      ($updates{$package}) = $raw_changelog =~ /$changelog_regex/smo;
    }
  }
  
  if ($compact) {
    # create a reverse index of packages indexed by their changelog
    # entries
    my %reverse_updates;
    foreach my $package (keys %updates) {
      if (!defined($reverse_updates{$updates{$package}})) {
        $reverse_updates{$updates{$package}} = [];
      }
      push @{$reverse_updates{$updates{$package}}}, $package;
    }

    foreach my $changelog (keys %reverse_updates) {
      foreach my $package (sort(@{$reverse_updates{$changelog}})) {
        print_package_name($package);
        print "\n";
      }
      print(indent_changelog_lines($changelog));
      print "\n";
    }
  } else { # not compact output
    foreach my $package (sort(keys %updates)) {
      print_package_name($package);
      print "\n";
      print(indent_changelog_lines($updates{$package}));
      print "\n";
    }
  }
}

# given a changelog return a new version
# with each line indentd by two spaces.
sub indent_changelog_lines {
  my $changelog = shift; # again could be a blank changelog
  my $ret = '';
  
  foreach my $line (split /\n/, $changelog) {
    $ret .= "  $line\n";
  }
  return $ret;
}

# Given the path to an rpm's header (or the actual rpm)
# retrieve the changelog
sub get_changelog {
  my $filename = shift or die "get_changelog didn't get filename, bug";
  my $changelog;
  
  open RPM, $filename or return '!!! UNABLE TO EXTRACT CHANGELOG FROM HDLIST.  HDLIST AND SYNTHESIS FILES MAY BE OUT OF SYNC !!!';
  my ($entries) = get_header_header(\*RPM);
  my ($index) = get_header_index(\*RPM,$entries);
  my ($changelog_date) = get_header_tag_data(\*RPM,$index,1080,$entries);
  my ($changelog_name) = get_header_tag_data(\*RPM,$index,1081,$entries);
  my ($changelog_text) = get_header_tag_data(\*RPM,$index,1082,$entries);
  close RPM;

  for (my $changelog_pos = 0; $changelog_pos < scalar(@{$changelog_text}); $changelog_pos++) {
    my $date = strftime('%a %b %d %Y',localtime($changelog_date->[$changelog_pos]));
    $changelog .= "* $date $changelog_name->[$changelog_pos]\n$changelog_text->[$changelog_pos]\n\n";
  }
  return $changelog;
}

# give a filehandle to an rpm header file or full package
# retrieve the master header and the length of the header
sub get_header_header {
  my $fh = shift or die "get_header_header didn't get a file handle, bug";
  my $buf; # temporary buffer
  
  seek $fh, 0, 0; # go to the beginning of the file
  read $fh, $buf, 4;
  unless (2393761793 == unpack('N', $buf)) {
    # look for the right magic number and version in the header
    die "Magic number of version of header is wrong.  This likely means you have a corrupted hdlist file or the rpm from which the hdlist was generated was corrupt!";
  }
  seek $fh, 4, 1; # skip the expansaion area.
  read $fh, $buf, 4;
  my $entries = unpack('N',$buf);
  read $fh, $buf, 4;
  my $headerlen = unpack('N',$buf);
  return ($entries,$headerlen);
}


# give a filehandle to an rpm header file or full package
# and the entries output of the get_header_header() function
# generate and return a hash of the index of the rpm header.
sub get_header_index {
  my $fh = shift or die "get_header_index didn't get filehandle, bug";
  my $entries = shift or die "get_header_index didn't get master header, bug";
  my $buf; # temporary buffer
  my %index;

  if (!defined($entries) || !$entries) {
    return (undef);
  }

  seek $fh, 16, 0; # start of the index.
  
  my $index_entry_pos = 0;
  while ($index_entry_pos < $entries && !eof($fh)) {
    read $fh, $buf, 16;
    my ($tag,$type,$position,$count) = unpack('NNNN',$buf);
    $index{$tag} = {'type'=>$type, 'position'=>$position, 'count'=>$count};
    $index_entry_pos++;
  }
  if (eof($fh)) {
    die "Shouldn't have reached the end of the header file but we did, this means you either have a corrupted hdlist file or the rpm the hdlist was built against was corrupt!";
  }

  return \%index;
}

# given the filehandle of a rpm header file or full rpm
# the index of the tag data in the rpm as produced by
# get_header_index the tag you want and the master header
# as generated by get_header_header return the data for a
# given tag.
sub get_header_tag_data {
  my $fh = shift or die "get_header_tag_data didn't get filehandle, bug";
  my $index = shift or die "get_header_tag_data didn't get index, bug";
  my $tag = shift or die "get_header_tag_data didn't get tag, bug";
  my $entries = shift or die "get_header_tag_data didn't get entries, bug";
  my $buf; #temporary buffer
  my @data;

  seek $fh, (($entries + 1) * 16), 0; # seek to the start of the store
  if (defined($index->{$tag})) {
    seek $fh, $index->{$tag}->{'position'}, 1; # seek to the start of the data
  
    my $data_count = 0;
    while ($data_count < $index->{$tag}->{'count'} && !eof($fh)) {
      if ($index->{$tag}->{'type'} == 4) { # handles int32
        read $fh, $buf, 4;
        push @data, unpack('N',$buf);
      } elsif ($index->{$tag}->{'type'} == 8) { # handles string array
        my $string;
        do {
          if (eof($fh)) {
            last;
          }
          read $fh, $buf, 1;
          $string .= $buf;
        } until (ord($buf) == 0);
        if (eof($fh)) {
          die "Shouldn't have reached the end of the header file but we did, this means you either have a corrupted hdlist file or the rpm the hdlist was built against was corrupt!";
        }
        push @data, unpack('Z*',$string);
        $string = undef;
      }
      $data_count++;
    }
  }
  return \@data;
}

# How to use borrowed from rpmdrake and then rewritten...
# Accepts a package variable with name-version-release.arch
# queries the rpm database and returns a variable with the
# version-release.arch of the currently installed version.
sub find_installed_version {
  my ($package) = shift or die "find_installed_version didn't get package name, bug";
  my $db = URPM::DB::open or die q(Couldn't open RPM DB);
  my $version;
  
  $package =~ s/-[^-]+-[^-]+$//;
  $db->traverse_tag('name', [ $package ],
    sub { my $pkginfo = shift; $version = $pkginfo->version.'-'.$pkginfo->release.'.'.$pkginfo->arch });
  return $version;
}

# Given a package name (name-version-release.arch) give the name of the
# source rpm for the installed package in format:
# name-version-release.src.rpm
sub find_installed_version_srpm {
  my ($package) = shift or die "find_installed_version_srpm didn't get package name, bug";
  my $db = URPM::DB::open or die q(Couldn't open RPM DB);
  my $srpm;

  $package =~ s/-[^-]+-[^-]+$//;
  $db->traverse_tag('name', [ $package ],
    sub { my $pkginfo = shift; $srpm = $pkginfo->sourcerpm });
  return $srpm;
}

# For a list of media (array of medium hashes
# from urpm config.  Run the urpmi.update command.
sub urpmi_update {
  my (@media) = @_ or die "urpmi_udpate didn't get list of media, bug";
  my $media_args = build_media_args(1,@media) or die "empty set of media args, bug";
  my $update_command = "$URPMI_UPDATE_PATH $proxy $proxyuser $curl $wget $media_args 2>&1";
  my $update = `$update_command`;
  if ($? >> 8) {
    print STDERR "Update command: $update_command\nfailed with:\n";
    print STDERR $update;
    die;
  } elsif ($verbose) {
    print STDOUT "Executing update command: $update_command\n";
    print STDOUT ">>>>\n";
    print STDOUT $update;
    print STDOUT "<<<<\n";
  }
}

# Execute the --auto-select on a list of media (medium
# hashes from urpm) and return an array of packages.
sub urpmq {
  my (@media) = @_ or die "urpmq didn't get a soruce list, bug";
  my $media_args = build_media_args(0,@media) or die "urpmq built emtpy media argsi, bug";
  my $query_command = "$URPMQ_PATH -f $media_args --auto-select";
  my $query = `$query_command`;
  if ($? >> 8) {
    print STDERR "Query command: $query_command\nfailed with:\n";
    print STDERR $query;
    die;
  } elsif ($verbose) {
    print STDOUT "Executing query command: $query_command\n";
    print STDOUT ">>>>\n";
    print STDOUT $query;
    print STDOUT "<<<<\n";
  }
  return split /\s+/, $query;
}

# Version header.
sub header {
  print "urpmc v$VERSION - $DATE\n";
}



__END__

=pod

=head1 NAME

urpmc - user rpm change(s|log) i.e. outputs a list of packages to be updated
from a urpmi medium and the changes made to them.

=head1 SYNOPSIS

urpmc [options] [media]

  Options:
    --changelog              Output changelog data <defaults on>.
    --compact                Group packages together by identical
                             changelogs <defaults on>.
    --curl                   Use curl to update the hdlist files.
    -f                       Print version, release and arch of each
                             package listed.
    --help -h -?             Brief help message.
    --ignore                 Ignore the medium given. 
    --man                    Full documentation.
    -n                       Number of changelog entries to show
                             <default = 1>.
    --nameonly               Don't print the version/release on packages
                             just the name.
    --nochangelog            Do not output changelog data.
    --nocompact              Do not group packages by identical
                             changelogs.
    --noshowcurrent --nosc   Do not show the currently installed version.
    --notocurrent --notc     Do not show changelog to current installed
                             version.
    --noupdatemedia --noum   Don't run uprmi.update.
                             <defaults on for non-root users>.
    --proxy                  Use specified HTTP proxy.
    --proxyuser              Specify user and pass to use for proxy.
    --showcurrent --sc       Show the currently installed version
                             <defaults on>.
    --tocurrent --tc         List changelog up to currently installed
                             version. <defaults on>
    --update                 Uses all media flagged with the update flag.
    --updatemedia --um       Run urpmi.update.
                             <defaults on for root user>.
    --verbose -v             Show output of all commands.
    --version -V             Show the version of updatelist
    --wget                   Use wget to update the hdlist files.

=head1 OPTIONS

=over 8

=item B<--changelog>

Output changelog data. Defaults on.

=item B<--compact>

Group packages together by identical changelogs.  This makes it easier
to see packages that are produced by one SRPM and always get updated
together.  Defaults on.

=item B<--curl>

Use curl to update the hdlist files.  This is really just a pass
through to urpmi.update.  See the L<uprmi.update> man page for details.

=item B<-f>

Print version, release and arch of each packages listed.  Same as in L<urpmq>.

=item B<--help -h -?>

Brief help message.

=item B<--ignore>

Ignores the given medium, useful for when run with no args and you don't want
to see the output from a specific medium.

=item B<--man>

Full documentation.

=item B<-n>

Number of changelog entries to show.  Passing 0 is equivalent
to using the --nochangelog option.  Defaults to 1.

=item B<--nameonly>

Don't print the version/release on packages just the name.  This option
implicitly disables --showcurrent or --sc.

==item B<--nochangelog>

Do not output changelog data.  Similar to urpmq standard output.
The benefit here is we run the urpmi.update command and don't
output anything when doing it.

==item B<--nocompact>

Do not group packages by identical changelogs.  Meaning even if they
have duplicate changelogs we'll print each package and its changelog.

=item B<--noshowcurrent --nosc>

Doesn't show the current installed version.  If you want to use the
--nameonly option you must use this option.

=item B<--notocurrent --notc>

Doesn't show the changelog to the currently installed version, but
rather only shows the number of changelog entries per package as
specified by the -n option (or the default of 1 if -n is not
specified).

=item B<--noupdatemedia --noum>

Don't run uprmi.update. Defaults on for non-root users.

=item B<--proxy>

Use specified HTTP proxy.  This is just a passthrough to
urpmi.update, see the L<urpmi.update> man page for details.

=item B<--proxyuser>

Specify user and pass to use for proxy.  This is just a
passthrough to urpmi.update, see the L<urpmi.update> man
page for details.

=item B<--showcurrent --sc>

Show the currently installed version.  Tied to the --nameonly option.
If you use this option --nameonly is unset.  Defaults on.

=item B<--tocurrent --tc>

List changelog up to currently installed version.  It does this by
getting the version of the source rpm that built the currently installed
version.   This should allow reliable limiting to the changes since what
you have installed.  There are a few exceptions.  Obviously if the package
isn't already installed you will get the full changelog.  If the package
doesn't have the changelog for the version you have intalled you will
also get the packages entire changelog.  This can happen if the changelog
has been truncated.  Defaults on.

=item B<--update>

Selects the media flagged with the update flag in the urpmi.cfg.
This can be done when adding it by urpmi.addmedia with --update or
by editing the urpmi.cfg.

=item B<--updatemedia --us>

Run urpmi.update. Defaults on for root user.

=item B<--verbose -v>

Show more output including the commands run and their output.
Primarily useful for debugging.

=item B<--version -V>

Show the version of urpmc

=item B<-wget>

Use wget to update the hdlist files.  This is really just a pass
through to urpmi.update.  See the L<uprmi.update> man page for details.

=back

=head1 DESCRIPTION

B<urpmc> will run urpmi.update on a medium or media, get the list of
packages that an auto-select would install, and show the changelogs of
those packages from the hdlist.

If you do not pass any media names or the --update option it will use
all media that are defined but not disabled/ignored.

If any of the selected media do not have a hdlist available then only
the names of changed packages will be printed.

If you pass conflicting options the last option on the command line
will be the one used.  For example if you pass, --notc --tc, --tc
would be used.

This program should be suitable for adding to a cronjob so you can see
what updates you need to install and why.  Especially useful for cooker
developers so they can see what changes they are installing, not just
the package names.

=head1 PREREQUISITES

This script requires the C<strict> module, C<warnings> module,
C<sigtrap> module, C<urpm> module, C<URPM> module, C<Getopt::Long> module,
C<Pod::Usage> module, C<packdrake> module, C<POSIX> module,
C<File::Temp> module.

=head1 TO DO

=over 8

=item * Possibly add the option to actually install the pacakges if being run interactively.

=item * Deal with making sure it gets the headers from the right medium if two media have the same file.

=back

=head1 AUTHOR

Ben Reser <ben@reser.org>

=head1 SPECIAL THANKS

trappist for proofing my documentation and beta testing.
vdanen and Grimau also for beta testing.

=head1 COPYRIGHT

Copyright (c) 2003 Ben Reser <ben@reser.org>

Licensed under the GPL.  Please see F<COPYING> for details.

=cut
