File prepare_spec of Package obs-service-format_spec_file.374

#! /usr/bin/perl -w
#
# vim:sw=2:et
# 

BEGIN {
  unshift @INC, ".";
  unshift @INC, "/usr/lib/build/";
}

use Time::localtime;
use Data::Dumper;
use strict;

my @oldspec = ();
my @newspec = ();
my $base_package = "";
my $icecreamforbuild = "";
my @copyrights = ();
my $needsrootforbuild = 0;
my $needsbinariesforbuild = 0;
my $nodebuginfo = 0;
my $vim_modeline;
my $nosrc_result = 0;
my $current_section = "header";
my $had_debug_package = 0;
my $main_license;
my %seen_licenses = ();
my $main_group;
my %seen_groups = ();
my $build_root = $ENV{'BUILD_ROOT'};
my $disabled_packs;
my $ifhandler;
my $definelist;
my $debug = 0;

my @global_tags_list = (
  'Autoreq',
  'Autoreqprov',
  'BuildArch',
  'BuildArchitectures',
  'BuildRequires',
  'Conflicts',
  'DocDir',
  'Enhances',
  'Enhances',
  'EssentialFor',
  'ExcludeArch',
  'ExclusiveArch',
  'Freshens',
  'Group',
  'Name',
  'NoPatch',
  'NoSource',
  'Obsoletes', 
  'Patch\d*',
  'Prefix',
  'PreReq',
  'Provides',
  'Recommends',
  'Requires',
  'Source\d*',
  'Suggests',
  'Summary',
  'Supplements',
  'Url',
);

my $global_tags_re = '^\s*(' . join("|", @global_tags_list) . ')\s*:';

my $section_tags_re ='^\s*%(?:clean|check|prep|build|install|pre|post|preun|postun|posttrans|package|' .
  'description|files|triggerin|triggerun|triggerpostun)\b';

sub unify {
  my %h = map {$_ => 1} @_;
  return grep(delete($h{$_}), @_);
}

sub capitalize_case($) {
  my ($tag) = @_;

  $tag = lc($tag);

  $tag =~ s/docdir/DocDir/i;
  $tag =~ s/arch/Arch/i;
  $tag =~ s/patch/Patch/i;
  $tag =~ s/source/Source/i;
  $tag =~ s/req/Req/i;
  $tag =~ s/prov/Prov/i;
  $tag =~ s/^(\w)/uc($1)/e;

  return $tag;
}

sub compare_arrays {
  my ($first, $second) = @_;
  return 0 unless @$first == @$second;
  for (my $i = 0; $i < @$first; $i++) {
    return 0 if $first->[$i] ne $second->[$i];
  }
  return 1;
}

sub maybe_add_empty_line() {
  return if $current_section eq "description";

  push @oldspec, "XXXBLANKLINE"
    if ($oldspec[-1] !~ /^\s*$/ && $oldspec[-1] !~ /^[#%]/);
}

sub change_section($) {
  my ($new_section) = @_;

  maybe_add_empty_line();

  $current_section = $new_section;
  warn "section changed to $current_section\n" if $debug;
}

my %license_replace = ();
use File::Basename;

sub load_license_map() {
  return if defined $license_replace{"GPL-2.0"};
  my $scriptdir = File::Basename::dirname($0);
  open(MAP, "$scriptdir/licenses_changes.txt") || die "can't open licenses_changes.txt";
  # ignore header
  readline(*MAP);
  my %spdx;
  while (<MAP>) {
    chomp;
    my ($license, $oldstring) = split(/\t/, $_, 2);
    #$license =~ s,\s*$,,;
    #$oldstring =~ s,\s*$,,;
    next unless length($license);
    #print STDERR "$license\t$oldstring\n";
    die "$oldstring is given twice in $_" if defined $license_replace{$oldstring};
    $license_replace{$oldstring} = $license;
    $spdx{$license} = 1;
  }
  close(MAP);
  for (keys %spdx) {
    $license_replace{$_} = $_;
  }
}

sub replace_single_spdx($) {
  my ($l) = @_;

  return '' if $l eq '';

  load_license_map();
  $l =~ s,ORlater,or later,g;
  $l =~ s,ORsim,or similar,g;
  $l =~ s,^\s+,,;
  $l =~ s,\s+$,,;

  if (defined $license_replace{$l}) {
    $l = $license_replace{$l};
  } else {
    print STDERR "Unknown license '$l'\n";
  }
  return $l;
}

sub replace_spdx_and($);
sub replace_spdx_and($) {
  my ($license) = @_;

  # special case as or later is common in our spec files
  $license =~ s,or later,ORlater,g;
  $license =~ s,or similar,ORsim,g;

  #print STDERR "ORIG '$license'\n";
  my @licenses = ();
  if ( $license =~ /^(.*?)\(([^)]*)\)(.*?)$/ ) {
    my ($head, $paren, $tail) = ($1, $2, $3);
    if ($paren =~ /and|or/) {
      $head = replace_spdx_and($head);
      $tail = replace_spdx_and($tail);
      $paren = replace_spdx_and($paren);
      #print STDERR "AFTE '$head($paren)$tail'\n";
      return "$head($paren)$tail";
    }
  }

  for (split(/(\s+(?:and|or)\s+)/, $license, -1)) {
    $_ = replace_single_spdx($_) unless $_ eq '' || /(\s+(?:and|or)\s+)/;
    s/\s+/ /g;
    push @licenses, $_;
  }
  #print STDERR "AFTE '" . join('', @licenses) . "'\n";
  return join('', @licenses);
}

sub replace_spdx($) {
  my ($license) = @_;

  my @licenses = ();
  for (split(/\s*;\s*/, $license)) {
    push @licenses, replace_spdx_and($_);
  }
  return join(' ; ', @licenses);
}

sub set_current_pkg {
  my ( $arg ) = @_;
  print "DEBUG: set_current_pkg receiving $arg\n" if $debug;
  my ( @argarray ) = split ( '\s+' , $arg );
  my $curpack = $base_package;
  my $curlang = "";
  while (my $carg = shift @argarray) {
    next if ($carg eq "%description" || $carg eq "%package" || $carg eq "%prep");
    if ($carg eq "-l") {
      $curlang = shift @argarray;
    } elsif ($carg eq "-n") {
      $curpack = shift @argarray;
    } else {
      $curpack = "$base_package-" if $base_package;
      $curpack .= $carg;
    }
  }
  print "DEBUG: set_current_pkg returning $curpack, $curlang\n" if $debug;
  return ($curpack, $curlang);
}

sub sort_tags_helper {
  if (($a =~ /^[^#]*\(/) != ($b =~ /^[^#]*\(/)) {
    if ($a =~ /^[^#]*\(/) {
      1;
    } else {
      -1;
    }
  } else {
    $a cmp $b;
  }
}

sub read_and_parse_old_spec {
  my ( $specfile, $base_package ) = @_;
  my $current_package = $base_package;
  my $current_lang = "";
  my $check_printed = "false";
  my $print_comments = "false";
  my %version;
  my $ifhandler;
  $ifhandler->{"disabled"} = 0;

  my @readspec;
  open ( SPEC , "$specfile" ) || die "can't read specfile";
  @readspec = <SPEC>;
  close SPEC;
  chomp @readspec;

  while (@readspec) {
    $_ = shift @readspec;

    if ( /^\s*$/ && $current_section ne "description") {
      # stop preamble parsing on two blank lines
      if ($print_comments eq "false" && 
	    $oldspec[0] && $oldspec[-1] eq "XXXBLANKLINE") {
	$print_comments = "true";
	push @oldspec, "XXXDOUBLELINE";
	next;
      }
      push @oldspec, "XXXBLANKLINE";
      next;
    }

    if ( /^# vim:/ ) {
      $vim_modeline = $_;
      next;
    }

    if ( /^#\s*needsrootforbuild\s*$/ ) {
      $needsrootforbuild = 1;
      next;
    }
    if ( /^#\s*needsbinariesforbuild\s*$/ ) {
      $needsbinariesforbuild = 1;
      next;
    }
    if ( /^#\s*norootforbuild/ ) {
      next;
    }

    if ( /^#\s*nodebuginfo\s*$/ ) {
      $nodebuginfo = 1;
      next;
    }
    if ( /^#\s*icecream/ ) {
      $icecreamforbuild = $_;
      $icecreamforbuild =~ s/^#\s*icecream\s*//;
      next;
    }
    if ( /^#\s*Copyright\s*/ ) {
      my $lastlineblank = 0;
      for (;;) {
	# check if line is ONLY a standard copyright line, if so, ignore.
	my $c = $_;
	$c =~ s{\s*(\d+|copyrights?|\(c\)|suse|linux|products|gmbh|nuremberg|n..?rnberg|germany|\W+)\s*}{}gi;
	push(@copyrights, $_) if length $c > 5;
	last if length $readspec[0] < 10 || $readspec[0] =~ m{modifications and additions}i || $readspec[0] !~ /^[\#\s]/
	  || grep { $readspec[0] =~ /^#\s*$_/ } ("needsrootforbuild","needsbinariesforbuild","nodebuginfo","icecream","usedforbuild","Commandline","MD5SUM","!BuildIgnore");
	$_ = shift @readspec;
      }
      next;
    }
    # evil epoch removal
    next if ( /^Epoch:/ );
    $_ =~ s/%{?epoch}?[:-]//g;
    $_ =~ s/ 0:/ /g if ( /^requires/i || /^buildreq/i );

    if ( /^BuildRequires:/i || /^Requires:/i || /^Provides:/i || /^Obsoletes:/i ) {
      my $cur_tag = $_;
      my $tag = '';
      if (m/^(\S+):\s*(.*)$/) {
	$tag = $1;
	$cur_tag = $2;
      }

      my %aa;
      while ($cur_tag =~ m{([^,\s]+(\s*[<=>]+\s*(?:\%\(.*\)|[^,\s]+))?)}g) {
	$aa{$1}=1;
      }
      # ignore line if it looks like a "usedforbuild" line, i.e.
      # if it contains too many base packages
      next if (grep {$aa{$_}} qw{gcc rpm glibc bash}) > 2;
      for my $value (sort keys(%aa)) {
	push (@oldspec, sprintf("%-16s%s", capitalize_case($tag) . ":", $value));
      }
      next;
    }
    next if ( /^#\s*usedforbuild/ );
    if ( /^%\?__\*BuildRequires:/ ) {
      push @oldspec, $_;
      next;
    }
    if ( /^#!__\*BuildRequires:/ ) {
      push @oldspec, $_;
      next;
    }
    if ( /^#!BuildIgnore:/ ) {
      push @oldspec, $_;
      next;
    }

    if ( /^#/ && $current_section ne "description") {
      warn "$_ $current_section\n" if $debug;
      if ( $print_comments eq "true" || $readspec[0] =~ /^%define/ || $readspec[0] =~ /^%if/) {
	push @oldspec, $_;
      }
      next;
    }

    if ( /^%debug_package/ ) {
      # remove, we add this ourselves
      next;
    }
    $print_comments = "true" unless /^#/;

    if ( /^%define\s*vendor\s/ || /^%define\s*distribution\s/ ) {
      next;
    }

    if ( /^\s*%if/ || /^\s*%\{/ || /^\s*%define/ || /^\s*%el/ || /^\s*%endif/ ) {
      change_section("header") if ($current_section eq "description");
      push @oldspec, $_;
      if ( /^\s*%if\s/ ) {
	my @args = split (/\s+/,$_);
	$_ =~ s/[\{\}\"]//g for (@args);
	$ifhandler->{"last_if_disabled"} = 0;
	$ifhandler->{"last_if_if"} = 1;
	$ifhandler->{"depth"}++;
	my $if_not = 0;
	if ( $args[1] =~ /^\!/ ) {
	  $args[1] =~ s/^\!//;
	  $if_not = 1;
	}
	$args[2] = "" unless $args[2];
	if (	($args[1] eq "0")
		  || ($args[1] eq "%name" && $args[2] eq "!=" && $args[3] eq $base_package)
		    || ($args[1] eq "%name" && $args[2] eq "==" && $args[3] ne $base_package)
		      || ($args[1] && !$args[3] && !$if_not && $definelist->{$args[1]} && $definelist->{$args[1]} eq "0")
			|| ($args[2] eq "==" && $args[3] ne "0" && $definelist->{$args[1]} && $definelist->{$args[1]} eq "0")
			  || ($args[2] eq "!=" && $args[3] eq "0" && $definelist->{$args[1]} && $definelist->{$args[1]} eq "0")
			    || ($args[1] && !$args[3] && $if_not && $definelist->{$args[1]} && $definelist->{$args[1]} eq "1")
			      || ($args[1] && $args[2] eq "!=" && $args[3] eq "1" && $definelist->{$args[1]} && $definelist->{$args[1]} eq "1") ) {
	  $ifhandler->{"disabled"} = $ifhandler->{"depth"};
	  $ifhandler->{"last_if_disabled"} = 1;
	}
      } elsif ( /^\s*%if/ ) {
	$ifhandler->{"last_if_disabled"} = 0;
	$ifhandler->{"last_if_if"} = 0;
	$ifhandler->{"depth"}++;
      } elsif ( /^\s*%endif/ ) {
	$ifhandler->{"disabled"} = 0 if $ifhandler->{"disabled"} == $ifhandler->{"depth"};
	$ifhandler->{"depth"}--;
      } elsif ( /^\s*%else/ ) {
	if ($ifhandler->{"disabled"} == $ifhandler->{"depth"} && $ifhandler->{"last_if_disabled"} == 1) {
	  $ifhandler->{"disabled"} = 0;
	} elsif ($ifhandler->{"disabled"} == 0 && $ifhandler->{"depth"} == 1 && $ifhandler->{"last_if_if"} == 1) {
	  $ifhandler->{"disabled"} = 1;
	}
      } elsif ( /^\s*%define\s/ ) {
	my @args = split (/\s+/,$_);
	$_ =~ s/[\{\}\"]//g for (@args);
	$args[2] =~ s/\Q$_\E/$definelist->{$_}/g for sort { length($b) <=> length($a) } keys (%{$definelist});
	if ( $args[2] !~ /[\(\)\{\}\@\%\"\\]/ ) {
	  $definelist->{"%".$args[1]} = $args[2] if $ifhandler->{"disabled"} == 0;
	  $definelist->{"%{".$args[1]."}"} = $args[2] if $ifhandler->{"disabled"} == 0;
	  $definelist->{"%{?".$args[1]."}"} = $args[2] if $ifhandler->{"disabled"} == 0;
	}
	while ($_ =~ /\\$/) {
	  $_ = shift @readspec;
	  push @oldspec, $_;
	}
      }
      next;
    }
    if ( /^%package\b/i or /^%prep\b/i ) {
      if (/^%package\b/i) {
	change_section("header");
      } else {
	change_section("prep");
      }
      $_ =~ s/^(%\w+)/lc($1)/e;
      if ($debug) {
	warn "key: $_ value: $definelist->{$_}\n" for (sort { length($b) <=> length($a) } keys (%{$definelist}));
      }
      push @oldspec, $_;
      for my $xx (sort { length($b) <=> length($a) } keys (%{$definelist})) {
	$_ =~ s/\Q$xx\E/$definelist->{$xx}/;
      }
      $_ =~ s/%{\?[^\}]*}//;
      if ($debug) {
	warn "after: $_\n";
      }
      ($current_package, $current_lang) = set_current_pkg ( $_ );
      if ($ifhandler->{"disabled"}) {
	$disabled_packs->{$current_package} = 1;
	warn "$current_package is disabled\n" if $debug;
      }
      next;
    }
    if ( /^%description\b/i ) {
      change_section("description");
      push @oldspec, $_;
      next;
    }
    if ( /^%install\b/i ) {
      change_section("install");
      push @oldspec, $_;
      next;
    }
    if ( /^%changelog\b/i ) {
      change_section("changelog");
      # changelog comes always from *.changes.	Skip what is in spec file
      # at the moment.
      next;
    }
    if (/^%files\b/i) {
      change_section("files");
      $current_section = "files";
    }
    if ( /^%/ ) {
      if ( m/$section_tags_re/oi ) {
	$_ =~ s/^(%\w+)/lc($1)/e;
	change_section("header") if (! m/\s*%files/i && !m/\s*%build/i);
	change_section("build") if m/\s*%build/i;
	warn "changed to $current_section for $_\n" if $debug;
      }

      push @oldspec, "$_";
      next;
    }

    if ($current_section eq "header") {
      my $c_pack = $current_package;
      $c_pack .= "_disabled" if $ifhandler->{"disabled"};

      if ( /^Vendor:/ || /^Distribution:/ || /^Packager:/ ) {
	next;
      }
      # remove default value of Autoreqprov
      if ( /^Autoreqprov\s*:\s*(.*)/i ) {
	next if ( lc($1) eq "on" || lc($1) eq "yes");
      }
      # reset Release
      if ( /^Release\s*:\s*(.*)/i ) {
	if ($1 !~ m/^[0-9]*$/ && $oldspec[-1] eq "XXXRELEASE") {
	  pop @oldspec;
	  push @oldspec, sprintf("%-16s%s","Release:", $1);
	}
	# will be after Version
	next;
      }
      if ( /^Summary\s*:\s*(.*)\s*$/i ) {
	push @oldspec, sprintf("%-16s%s", "Summary:", $1);
	push @oldspec, "XXXPOSTSUMMARY $current_package";
	next;
      }

      # remove license and print out after license later
      if ( /^License\s*:\s*(.*)\s*$/i || /^Copyright\s*:\s*(.*)\s*$/i ) {
	my $license = replace_spdx($1);
	$main_license = $license if (!$main_license);
	$seen_licenses{$current_package} = $license;
	next;
      }

      # remove groups and print out after summary later
      if ( /^Group\s*:\s*(.*)\s*$/i ) {
	my $group = $1;
	$main_group = $group if (!$main_group);
	$seen_groups{$current_package} = $group;
	next;
      }

      if ( /^BuildArchitectures\s*:/i ) {
	$_ =~ s/^[^:]+:/BuildArch:/;
      }

      if ( /^BuildRoot\s*:/i ) {
	push @oldspec, sprintf("%-16s%s", "BuildRoot:", "%{_tmppath}/%{name}-%{version}-build");
	next;
      }

      if ( m/$global_tags_re\s*(.*)/oi ) {
	my ($tag, $value) = ($1, $2);
	$nosrc_result = 1 if ($tag =~ /(?:nosource|nopatch)/i);
	push @oldspec, sprintf("%-16s%s", capitalize_case($tag) . ":", $value);
	next;
      }
      if ( /^Version:/ ) {
	warn "found Version, section = $current_section\n" if $debug;
	$version{$c_pack} = $_;
	$version{$c_pack} =~ s/^Version:\s*(.*)\s*/$1/;
	push @oldspec, sprintf("%-16s%s","Version:",$version{$c_pack});
	push @oldspec, "XXXRELEASE";
	next;
      }
    }
    if ( $current_section ne "changelog" ) {
      push @oldspec, $_;
      next;
    }
  }

}

if ($ARGV[0] eq '--debug') {
  $debug = 1;
  shift @ARGV;
}

my $specfile = shift ( @ARGV );
if ( ! stat($specfile) ) {
  die "$specfile is no file";
}


my @specpath = split ('/' ,$specfile);
my $specbase = pop @specpath;
my $specdir = join ('/', @specpath);

if ( $specdir eq "" ) {
  $specdir = ".";
}

my $xdefinelist;
my $seen_name = 0;
open ( SPE , "$specfile" );
while ( <SPE> ) {
  chomp();
  if ( /^%define/ ) {
    my @args = split (/\s+/,$_);
    $_ =~ s/[\{\}\"]//g for (@args);
    $args[2] =~ s/\Q$_\E/$xdefinelist->{$_}/ for (sort { length($b) <=> length($a) } keys (%{$xdefinelist}));
    if ( $args[2] !~ /[\(\)\{\}\@\%\"\\]/ ) {
      $xdefinelist->{"%".$args[1]} = $args[2];
      $xdefinelist->{"%{".$args[1]."}"} = $args[2];
    }
  }
  if ( /^\s*Name:/ ) {
    next if $seen_name;
    $seen_name = 1;
    $base_package = $_;
    $base_package =~ s/^\s*Name:\s*(\S*)\s*/$1/;
    $base_package =~ s/\Q$_\E/$xdefinelist->{$_}/ for (sort { length($b) <=> length($a) } keys (%{$xdefinelist}));
    if ($debug) {
      warn "DEBUG: base_package = $base_package\n";
      warn Dumper($xdefinelist);
    }
    last;
  }
}
close ( SPE );

warn ("base_package is $base_package\n") if $debug;

if ( ! stat ((glob("$specdir/$base_package*.spec"))[0] || "") ) {
  $base_package =~ s/[0-9]$//;
}

if ( ! stat ((glob("$specdir/$base_package*.spec"))[0] || "") ) {
  $base_package =~ s/\-[^\-]*$//;
}

warn ("base_package is $base_package\n") if $debug;

if ( ! stat ((glob("$specdir/$base_package*.spec"))[0] || "") ) {
  $base_package = $specbase;
  $base_package =~ s/\.spec$//;
  $base_package =~ s/\-.*$//;
}

read_and_parse_old_spec ( $specfile, $base_package );

for my $tag (qw(BuildRequires Requires Provides Obsoletes)) {
  my $linesmoved = 1;
 sortcycle: while ($linesmoved) {
    $linesmoved = 0;
    my @firstlines = ();
    my @tags = ();
    while (defined $oldspec[0]) {
      my $l = shift @oldspec;
      if ($l =~ m/^$tag:/i ) {
	push(@tags, $l);
      } else {
	# if there are already tags, we need to sort and exit
	if (@tags > 0) {
	  my @sortedtags = sort sort_tags_helper @tags;
	  $linesmoved = !compare_arrays(\@tags, \@sortedtags);
	  if ($linesmoved) {
	    @oldspec = (@firstlines, @sortedtags, $l, @oldspec);
	    @firstlines = ();
	    @tags = ();
	    next sortcycle;
	  } else {
	    @firstlines = (@firstlines, @tags, $l);
	    @tags = ();
	    next;
	  }
	} else {
	  push(@firstlines, $l);
	}
      }
    }
    @oldspec = (@firstlines, @oldspec);
  }
}


my $thisyear = localtime->year() + 1900;

unshift @copyrights, "# Copyright (c) $thisyear SUSE LINUX Products GmbH, Nuernberg, Germany.";

my $copy_list = join("\n", @copyrights);

print $vim_modeline . "\n" if (defined $vim_modeline);
print <<EOF;
#
# spec file for package $base_package
#
$copy_list
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.

# Please submit bugfixes or comments via http://bugs.opensuse.org/
#
EOF

print "# needsrootforbuild\n" if $needsrootforbuild;
print "# needsbinariesforbuild\n" if $needsbinariesforbuild;
print "# nodebuginfo\n" if $nodebuginfo;
print "# icecream $icecreamforbuild\n" if $icecreamforbuild ne '';
#print "\n" if ($needsrootforbuild || $needsbinariesforbuild || $nodebuginfo || $icecreamforbuild ne '');

while ($oldspec[0] eq "XXXBLANKLINE" || $oldspec[0] eq "XXXDOUBLELINE") {
  shift @oldspec;
}

print "\n\n";

my $license_unique = !grep { $_ ne $main_license } values %seen_licenses;
my $groups_unique = !grep { $_ ne $main_group } values %seen_groups;
# we need to have unique groups in the spec file as long as we support SLE11 ;(
$groups_unique = 0;
my $first_summary = 1;

my $line;
while (@oldspec) {
  $line = shift @oldspec;

  if ($line eq "XXXBLANKLINE") {
    print "\n" unless $oldspec[0] && ($oldspec[0] =~ m/^XXX.*LINE/ || $oldspec[0] =~ /^\s*$/ || $oldspec[0] =~ /^\n/);
  } elsif ($line eq "XXXDOUBLELINE") {
    print "\n\n" unless $oldspec[0] && ($oldspec[0] =~ m/^XXX.*LINE/ || $oldspec[0] =~ /^\s*$/ || $oldspec[0] =~ /^\n/);
  } elsif ($line eq "XXXRELEASE") {
    printf("%-16s%s\n", "Release:", "0") ;
  } elsif ($line =~ m/XXXPOSTSUMMARY (.*)$/) {
    my $current_package = $1;
    my $license = $seen_licenses{$current_package} || $main_license;
    printf("%-16s%s\n", "License:", $license) if (!$license_unique || $first_summary);
    my $group = $seen_groups{$current_package} || $main_group;
    printf("%-16s%s\n", "Group:", $group) if (!$groups_unique || $first_summary);
    $first_summary = 0;
  } else {
    print "$line\n";
  }
}

print "\n" unless $line eq "XXXBLANKLINE";
print "%changelog\n";