|
@@ -0,0 +1,289 @@
|
|
|
|
|
+#!/usr/bin/perl
|
|
|
|
|
+
|
|
|
|
|
+# Copyright © 2009-2010 Raphaël Hertzog <hertzog@debian.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, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
+
|
|
|
|
|
+use warnings;
|
|
|
|
|
+use strict;
|
|
|
|
|
+
|
|
|
|
|
+use Dpkg;
|
|
|
|
|
+use Dpkg::Changelog::Debian;
|
|
|
|
|
+use Dpkg::ErrorHandling;
|
|
|
|
|
+use Dpkg::Gettext;
|
|
|
|
|
+use Dpkg::Version;
|
|
|
|
|
+
|
|
|
|
|
+use Getopt::Long;
|
|
|
|
|
+use Scalar::Util qw(blessed);
|
|
|
|
|
+
|
|
|
|
|
+sub merge_entries($$$);
|
|
|
|
|
+sub merge_block($$$;&);
|
|
|
|
|
+sub merge_entry_item($$$$);
|
|
|
|
|
+sub merge_conflict($$);
|
|
|
|
|
+sub get_conflict_block($$);
|
|
|
|
|
+sub join_lines($);
|
|
|
|
|
+
|
|
|
|
|
+BEGIN {
|
|
|
|
|
+ eval 'use Algorithm::Merge qw(merge);';
|
|
|
|
|
+ if ($@) {
|
|
|
|
|
+ eval q{
|
|
|
|
|
+ sub merge {
|
|
|
|
|
+ my ($o, $a, $b) = @_;
|
|
|
|
|
+ return @$a if join("\n", @$a) eq join("\n", @$b);
|
|
|
|
|
+ return get_conflict_block($a, $b);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub version {
|
|
|
|
|
+ printf _g("Debian %s version %s.\n"), $progname, $version;
|
|
|
|
|
+
|
|
|
|
|
+ printf "\n" . _g("Copyright (C) 2009-2010 Raphaël Hertzog.");
|
|
|
|
|
+
|
|
|
|
|
+ printf "\n" . _g(
|
|
|
|
|
+"This is free software; see the GNU General Public License version 2 or
|
|
|
|
|
+later for copying conditions. There is NO warranty.
|
|
|
|
|
+");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub usage {
|
|
|
|
|
+ printf(_g(
|
|
|
|
|
+"Usage: %s [<option> ...] <old> <new-a> <new-b> [<out>]
|
|
|
|
|
+
|
|
|
|
|
+Options:
|
|
|
|
|
+ --merge-prereleases, -m merge pre-releases together, ignores everything
|
|
|
|
|
+ after the last '~' in the version.
|
|
|
|
|
+ --help, -h, -? show this help message.
|
|
|
|
|
+ --version show the version.
|
|
|
|
|
+"), $progname);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+my $merge_prereleases;
|
|
|
|
|
+unless (GetOptions('help|?' => sub { usage(); exit(0) },
|
|
|
|
|
+ 'merge-prereleases|m' => \$merge_prereleases,
|
|
|
|
|
+ 'version' => sub { version(); exit(0) })) {
|
|
|
|
|
+ usage();
|
|
|
|
|
+ exit(2);
|
|
|
|
|
+}
|
|
|
|
|
+my ($old, $new_a, $new_b, $out_file) = @ARGV;
|
|
|
|
|
+unless (defined $old and defined $new_a and defined $new_b and
|
|
|
|
|
+ -e $old and -e $new_a and -e $new_b)
|
|
|
|
|
+{
|
|
|
|
|
+ usage();
|
|
|
|
|
+ exit(2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+my ($cho, $cha, $chb, $res);
|
|
|
|
|
+$cho = Dpkg::Changelog::Debian->new();
|
|
|
|
|
+$cho->load($old);
|
|
|
|
|
+$cha = Dpkg::Changelog::Debian->new();
|
|
|
|
|
+$cha->load($new_a);
|
|
|
|
|
+$chb = Dpkg::Changelog::Debian->new();
|
|
|
|
|
+$chb->load($new_b);
|
|
|
|
|
+
|
|
|
|
|
+my @o = reverse @$cho;
|
|
|
|
|
+my @a = reverse @$cha;
|
|
|
|
|
+my @b = reverse @$chb;
|
|
|
|
|
+
|
|
|
|
|
+my @result; # Lines to output
|
|
|
|
|
+my $exitcode = 0; # 1 if conflict encountered
|
|
|
|
|
+
|
|
|
|
|
+unless (merge_block($cho, $cha, $chb, sub {
|
|
|
|
|
+ my $tail = $_[0]->get_unparsed_tail();
|
|
|
|
|
+ chomp $tail if defined $tail;
|
|
|
|
|
+ return $tail;
|
|
|
|
|
+ }))
|
|
|
|
|
+{
|
|
|
|
|
+ merge_conflict($cha->get_unparsed_tail(), $chb->get_unparsed_tail());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+while (1) {
|
|
|
|
|
+ my ($o, $a, $b) = get_items_to_merge();
|
|
|
|
|
+ last unless defined $o or defined $a or defined $b;
|
|
|
|
|
+ next if merge_block($o, $a, $b);
|
|
|
|
|
+ # We only have the usually conflicting cases left
|
|
|
|
|
+ if (defined $a and defined $b) {
|
|
|
|
|
+ # Same entry, merge sub-items separately for a nicer result
|
|
|
|
|
+ merge_entries($o, $a, $b);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ # Non-existing on one side, changed on the other side
|
|
|
|
|
+ merge_conflict($a, $b);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+if (defined($out_file) and $out_file ne "-") {
|
|
|
|
|
+ open(OUT, ">", $out_file) || syserr(_g("cannot write %s"), $out_file);
|
|
|
|
|
+ print OUT ((blessed $_) ? "$_" : "$_\n") foreach @result;
|
|
|
|
|
+ close(OUT);
|
|
|
|
|
+} else {
|
|
|
|
|
+ print ((blessed $_) ? "$_" : "$_\n") foreach @result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+exit $exitcode;
|
|
|
|
|
+
|
|
|
|
|
+# Returns the next items to merge, all items returned correspond to the
|
|
|
|
|
+# same minimal version among the 3 possible next items (undef is returned
|
|
|
|
|
+# if the next item on the given changelog is skipped)
|
|
|
|
|
+sub get_items_to_merge {
|
|
|
|
|
+ my @items = (shift @o, shift @a, shift @b);
|
|
|
|
|
+ my @arrays = (\@o, \@a, \@b);
|
|
|
|
|
+ my $minver;
|
|
|
|
|
+ foreach my $i (0 .. 2) {
|
|
|
|
|
+ if (defined $minver and defined $items[$i]) {
|
|
|
|
|
+ my $cmp = compare_versions($minver, $items[$i]->get_version());
|
|
|
|
|
+ if ($cmp > 0) {
|
|
|
|
|
+ $minver = $items[$i]->get_version();
|
|
|
|
|
+ foreach my $j (0 .. $i - 1) {
|
|
|
|
|
+ unshift @{$arrays[$j]}, $items[$j];
|
|
|
|
|
+ $items[$j] = undef;
|
|
|
|
|
+ }
|
|
|
|
|
+ } elsif ($cmp < 0) {
|
|
|
|
|
+ unshift @{$arrays[$i]}, $items[$i];
|
|
|
|
|
+ $items[$i] = undef;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $minver = $items[$i]->get_version() if defined $items[$i];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return @items;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Compares the versions taking into account some oddities like the fact
|
|
|
|
|
+# that we want backport/volatile versions to sort higher than the version
|
|
|
|
|
+# on which they are based.
|
|
|
|
|
+sub compare_versions {
|
|
|
|
|
+ my ($a, $b) = @_;
|
|
|
|
|
+ return 0 if not defined $a and not defined $b;
|
|
|
|
|
+ return 1 if not defined $b;
|
|
|
|
|
+ return -1 if not defined $a;
|
|
|
|
|
+ $a = $a->get_version() if ref($a) and $a->isa("Dpkg::Changelog::Entry");
|
|
|
|
|
+ $b = $b->get_version() if ref($b) and $b->isa("Dpkg::Changelog::Entry");
|
|
|
|
|
+ # Backport and volatile are not real prereleases
|
|
|
|
|
+ $a =~ s/~(bpo|vola)/+$1/;
|
|
|
|
|
+ $b =~ s/~(bpo|vola)/+$1/;
|
|
|
|
|
+ if ($merge_prereleases) {
|
|
|
|
|
+ $a =~ s/~[^~]*$//;
|
|
|
|
|
+ $b =~ s/~[^~]*$//;
|
|
|
|
|
+ }
|
|
|
|
|
+ return version_compare($a, $b);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Merge changelog entries smartly by merging individually the different
|
|
|
|
|
+# parts constituting an entry
|
|
|
|
|
+sub merge_entries($$$) {
|
|
|
|
|
+ my ($o, $a, $b) = @_;
|
|
|
|
|
+ # NOTE: Only $o can be undef
|
|
|
|
|
+
|
|
|
|
|
+ # Merge the trailer line
|
|
|
|
|
+ unless (merge_entry_item("blank_after_trailer", $o, $a, $b)) {
|
|
|
|
|
+ unshift @result, "";
|
|
|
|
|
+ }
|
|
|
|
|
+ unless (merge_entry_item("trailer", $o, $a, $b)) {
|
|
|
|
|
+ merge_conflict($a->get_part('trailer'), $b->get_part('trailer'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Merge the changes
|
|
|
|
|
+ unless (merge_entry_item("blank_after_changes", $o, $a, $b)) {
|
|
|
|
|
+ unshift @result, "";
|
|
|
|
|
+ }
|
|
|
|
|
+ my @merged = merge(defined $o ? $o->get_part('changes') : [],
|
|
|
|
|
+ $a->get_part('changes'), $b->get_part('changes'),
|
|
|
|
|
+ {
|
|
|
|
|
+ CONFLICT => sub {
|
|
|
|
|
+ $exitcode = 1;
|
|
|
|
|
+ return get_conflict_block($_[0], $_[1]);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ unshift @result, @merged;
|
|
|
|
|
+
|
|
|
|
|
+ # Merge the header line
|
|
|
|
|
+ unless (merge_entry_item("blank_after_header", $o, $a, $b)) {
|
|
|
|
|
+ unshift @result, "";
|
|
|
|
|
+ }
|
|
|
|
|
+ unless (merge_entry_item("header", $o, $a, $b)) {
|
|
|
|
|
+ merge_conflict($a->get_part('header'), $b->get_part('header'));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub join_lines($) {
|
|
|
|
|
+ my $array = shift;
|
|
|
|
|
+ return join("\n", @$array) if ref($array) eq "ARRAY";
|
|
|
|
|
+ return $array;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Try to merge the obvious cases, return 1 on success and 0 on failure
|
|
|
|
|
+# O A B
|
|
|
|
|
+# - x x => x
|
|
|
|
|
+# o o b => b
|
|
|
|
|
+# - - b => b
|
|
|
|
|
+# o a o => a
|
|
|
|
|
+# - a - => a
|
|
|
|
|
+sub merge_block($$$;&) {
|
|
|
|
|
+ my ($o, $a, $b, $preprocess) = @_;
|
|
|
|
|
+ $preprocess = \&join_lines unless defined $preprocess;
|
|
|
|
|
+ $o = &$preprocess($o) if defined($o);
|
|
|
|
|
+ $a = &$preprocess($a) if defined($a);
|
|
|
|
|
+ $b = &$preprocess($b) if defined($b);
|
|
|
|
|
+ return 1 if not defined($a) and not defined($b);
|
|
|
|
|
+ if (defined($a) and defined($b) and ($a eq $b)) {
|
|
|
|
|
+ unshift @result, $a;
|
|
|
|
|
+ } elsif ((defined($a) and defined($o) and ($a eq $o)) or
|
|
|
|
|
+ (not defined($a) and not defined($o))) {
|
|
|
|
|
+ unshift @result, $b if defined $b;
|
|
|
|
|
+ } elsif ((defined($b) and defined($o) and ($b eq $o)) or
|
|
|
|
|
+ (not defined($b) and not defined($o))) {
|
|
|
|
|
+ unshift @result, $a if defined $a;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub merge_entry_item($$$$) {
|
|
|
|
|
+ my ($item, $o, $a, $b) = @_;
|
|
|
|
|
+ if (blessed($o) and $o->isa('Dpkg::Changelog::Entry')) {
|
|
|
|
|
+ $o = $o->get_part($item);
|
|
|
|
|
+ } elsif (ref $o) {
|
|
|
|
|
+ $o = $o->{$item};
|
|
|
|
|
+ }
|
|
|
|
|
+ if (blessed($a) and $a->isa('Dpkg::Changelog::Entry')) {
|
|
|
|
|
+ $a = $a->get_part($item);
|
|
|
|
|
+ } elsif (ref $a) {
|
|
|
|
|
+ $a = $a->{$item};
|
|
|
|
|
+ }
|
|
|
|
|
+ if (blessed($b) and $b->isa('Dpkg::Changelog::Entry')) {
|
|
|
|
|
+ $b = $b->get_part($item);
|
|
|
|
|
+ } elsif (ref $b) {
|
|
|
|
|
+ $b = $b->{$item};
|
|
|
|
|
+ }
|
|
|
|
|
+ return merge_block($o, $a, $b);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub merge_conflict($$) {
|
|
|
|
|
+ my ($a, $b) = @_;
|
|
|
|
|
+ unshift @result, get_conflict_block($a, $b);
|
|
|
|
|
+ $exitcode = 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub get_conflict_block($$) {
|
|
|
|
|
+ my ($a, $b) = @_;
|
|
|
|
|
+ my (@a, @b);
|
|
|
|
|
+ push @a, $a if defined $a;
|
|
|
|
|
+ push @b, $b if defined $b;
|
|
|
|
|
+ @a = @{$a} if ref($a) eq "ARRAY";
|
|
|
|
|
+ @b = @{$b} if ref($b) eq "ARRAY";
|
|
|
|
|
+ return ("<<<<<<", @a, "======", @b, ">>>>>>");
|
|
|
|
|
+}
|
|
|
|
|
+
|