| File: | dpkg-mergechangelogs.pl |
| Coverage: | 77.5% |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | #!/usr/bin/perl | ||||||
| 2 | |||||||
| 3 | # Copyright © 2009-2010 Raphaël Hertzog <hertzog@debian.org> | ||||||
| 4 | # Copyright © 2012 Guillem Jover <guillem@debian.org> | ||||||
| 5 | # | ||||||
| 6 | # This program is free software; you can redistribute it and/or modify | ||||||
| 7 | # it under the terms of the GNU General Public License as published by | ||||||
| 8 | # the Free Software Foundation; either version 2 of the License, or | ||||||
| 9 | # (at your option) any later version. | ||||||
| 10 | # | ||||||
| 11 | # This program is distributed in the hope that it will be useful, | ||||||
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||
| 14 | # GNU General Public License for more details. | ||||||
| 15 | # | ||||||
| 16 | # You should have received a copy of the GNU General Public License | ||||||
| 17 | # along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 18 | |||||||
| 19 | 12 12 12 | 21514 10 431 | use warnings; | ||||
| 20 | 12 12 12 | 28 4 212 | use strict; | ||||
| 21 | |||||||
| 22 | 12 12 12 | 21 5 626 | use Scalar::Util qw(blessed); | ||||
| 23 | 12 12 12 | 2415 76413 28 | use Getopt::Long qw(:config posix_default bundling_values no_ignorecase); | ||||
| 24 | |||||||
| 25 | 12 12 12 | 2996 8 143 | use Dpkg (); | ||||
| 26 | 12 12 12 | 1605 17 351 | use Dpkg::Changelog::Debian; | ||||
| 27 | 12 12 12 | 39 10 597 | use Dpkg::ErrorHandling; | ||||
| 28 | 12 12 12 | 31 4 296 | use Dpkg::Gettext; | ||||
| 29 | 12 12 12 | 25 4 374 | use Dpkg::Version; | ||||
| 30 | 12 12 12 | 24 6 1175 | use Dpkg::Vendor qw(run_vendor_hook); | ||||
| 31 | |||||||
| 32 | 12 | 557526 | textdomain('dpkg-dev'); | ||||
| 33 | |||||||
| 34 | sub merge_entries($$$); | ||||||
| 35 | sub merge_block($$$;&); | ||||||
| 36 | sub merge_entry_item($$$$); | ||||||
| 37 | sub merge_conflict($$); | ||||||
| 38 | sub get_conflict_block($$); | ||||||
| 39 | sub join_lines($); | ||||||
| 40 | |||||||
| 41 | BEGIN { | ||||||
| 42 | 12 12 12 12 | 397 2329 99638 363 | eval q{ | ||||
| 43 | use Algorithm::Merge qw(merge); | ||||||
| 44 | }; | ||||||
| 45 | 12 | 20183 | if ($@) { | ||||
| 46 | *merge = sub { | ||||||
| 47 | 0 | 0 | my ($o, $a, $b) = @_; | ||||
| 48 | 0 | 0 | return @$a if join("\n", @$a) eq join("\n", @$b); | ||||
| 49 | 0 | 0 | return get_conflict_block($a, $b); | ||||
| 50 | 0 | 0 | }; | ||||
| 51 | } | ||||||
| 52 | } | ||||||
| 53 | |||||||
| 54 | sub version { | ||||||
| 55 | 0 | 0 | printf g_("Debian %s version %s.\n"), $Dpkg::PROGNAME, $Dpkg::PROGVERSION; | ||||
| 56 | |||||||
| 57 | 0 | 0 | printf "\n" . g_( | ||||
| 58 | 'This is free software; see the GNU General Public License version 2 or | ||||||
| 59 | later for copying conditions. There is NO warranty. | ||||||
| 60 | '); | ||||||
| 61 | } | ||||||
| 62 | |||||||
| 63 | sub usage { | ||||||
| 64 | 0 | 0 | printf g_( | ||||
| 65 | "Usage: %s [<option>...] <old> <new-a> <new-b> [<out>] | ||||||
| 66 | |||||||
| 67 | Options: | ||||||
| 68 | -m, --merge-prereleases merge pre-releases together, ignores everything | ||||||
| 69 | after the last '~' in the version. | ||||||
| 70 | --merge-unreleased merge UNRELEASED entries together, ignoring their | ||||||
| 71 | version numbers. | ||||||
| 72 | -?, --help show this help message. | ||||||
| 73 | --version show the version. | ||||||
| 74 | "), $Dpkg::PROGNAME; | ||||||
| 75 | } | ||||||
| 76 | |||||||
| 77 | 12 | 17 | my $merge_prereleases; | ||||
| 78 | my $merge_unreleased; | ||||||
| 79 | |||||||
| 80 | my @options_spec = ( | ||||||
| 81 | 0 0 | 0 0 | 'help|?' => sub { usage(); exit(0) }, | ||||
| 82 | 0 0 | 0 0 | 'version' => sub { version(); exit(0) }, | ||||
| 83 | 12 | 83 | 'merge-prereleases|m' => \$merge_prereleases, | ||||
| 84 | 'merge-unreleased' => \$merge_unreleased, | ||||||
| 85 | ); | ||||||
| 86 | |||||||
| 87 | { | ||||||
| 88 | 12 12 0 | 8 44 0 | local $SIG{__WARN__} = sub { usageerr($_[0]) }; | ||||
| 89 | 12 | 34 | GetOptions(@options_spec); | ||||
| 90 | } | ||||||
| 91 | |||||||
| 92 | 12 | 2509 | my $backport_version_regex = run_vendor_hook('backport-version-regex'); | ||||
| 93 | |||||||
| 94 | 12 | 22 | my ($old, $new_a, $new_b, $out_file) = @ARGV; | ||||
| 95 | 12 | 105 | unless (defined $old and defined $new_a and defined $new_b) | ||||
| 96 | { | ||||||
| 97 | 0 | 0 | usageerr(g_('needs at least three arguments')); | ||||
| 98 | } | ||||||
| 99 | 12 | 157 | unless (-e $old and -e $new_a and -e $new_b) | ||||
| 100 | { | ||||||
| 101 | 0 | 0 | usageerr(g_('file arguments need to exist')); | ||||
| 102 | } | ||||||
| 103 | |||||||
| 104 | 12 | 12 | my ($cho, $cha, $chb); | ||||
| 105 | 12 | 81 | $cho = Dpkg::Changelog::Debian->new(); | ||||
| 106 | 12 | 39 | $cho->load($old); | ||||
| 107 | 12 | 80 | $cha = Dpkg::Changelog::Debian->new(); | ||||
| 108 | 12 | 21 | $cha->load($new_a); | ||||
| 109 | 12 | 70 | $chb = Dpkg::Changelog::Debian->new(); | ||||
| 110 | 12 | 24 | $chb->load($new_b); | ||||
| 111 | |||||||
| 112 | 12 | 37 | my @o = reverse @$cho; | ||||
| 113 | 12 | 14 | my @a = reverse @$cha; | ||||
| 114 | 12 | 11 | my @b = reverse @$chb; | ||||
| 115 | |||||||
| 116 | 12 | 12 | my @result; # Lines to output | ||||
| 117 | 12 | 7 | my $exitcode = 0; # 1 if conflict encountered | ||||
| 118 | |||||||
| 119 | sub merge_tail { | ||||||
| 120 | 36 | 21 | my $changes = shift; | ||||
| 121 | 36 | 65 | my $tail = $changes->get_unparsed_tail(); | ||||
| 122 | 36 | 31 | chomp $tail if defined $tail; | ||||
| 123 | 36 | 35 | return $tail; | ||||
| 124 | }; | ||||||
| 125 | |||||||
| 126 | 12 | 29 | unless (merge_block($cho, $cha, $chb, \&merge_tail)) { | ||||
| 127 | 0 | 0 | merge_conflict($cha->get_unparsed_tail(), $chb->get_unparsed_tail()); | ||||
| 128 | } | ||||||
| 129 | |||||||
| 130 | 12 | 10 | while (1) { | ||||
| 131 | 108 | 88 | my ($o, $a, $b) = get_items_to_merge(); | ||||
| 132 | 108 | 257 | last unless defined $o or defined $a or defined $b; | ||||
| 133 | 96 | 86 | next if merge_block($o, $a, $b); | ||||
| 134 | # We only have the usually conflicting cases left | ||||||
| 135 | 30 | 69 | if (defined $a and defined $b) { | ||||
| 136 | # Same entry, merge sub-items separately for a nicer result | ||||||
| 137 | 30 | 35 | merge_entries($o, $a, $b); | ||||
| 138 | } else { | ||||||
| 139 | # Non-existing on one side, changed on the other side | ||||||
| 140 | 0 | 0 | merge_conflict($a, $b); | ||||
| 141 | } | ||||||
| 142 | } | ||||||
| 143 | |||||||
| 144 | 12 | 29 | if (defined($out_file) and $out_file ne '-') { | ||||
| 145 | 0 | 0 | open(my $out_fh, '>', $out_file) | ||||
| 146 | or syserr(g_('cannot write %s'), $out_file); | ||||||
| 147 | 0 0 | 0 0 | print { $out_fh } ((blessed $_) ? "$_" : "$_\n") foreach @result; | ||||
| 148 | 0 | 0 | close($out_fh) or syserr(g_('cannot write %s'), $out_file); | ||||
| 149 | } else { | ||||||
| 150 | 12 | 132 | print ((blessed $_) ? "$_" : "$_\n") foreach @result; | ||||
| 151 | } | ||||||
| 152 | |||||||
| 153 | 12 | 235 | exit $exitcode; | ||||
| 154 | |||||||
| 155 | # Returns the next items to merge, all items returned correspond to the | ||||||
| 156 | # same minimal version among the 3 possible next items (undef is returned | ||||||
| 157 | # if the next item on the given changelog is skipped) | ||||||
| 158 | sub get_items_to_merge { | ||||||
| 159 | 108 | 95 | my @items = (shift @o, shift @a, shift @b); | ||||
| 160 | 108 | 97 | my @arrays = (\@o, \@a, \@b); | ||||
| 161 | 108 | 57 | my $minitem; | ||||
| 162 | 108 | 105 | foreach my $i (0 .. 2) { | ||||
| 163 | 324 | 421 | if (defined $minitem and defined $items[$i]) { | ||||
| 164 | 162 | 130 | my $cmp = compare_versions($minitem, $items[$i]); | ||||
| 165 | 162 | 218 | if ($cmp > 0) { | ||||
| 166 | 15 | 13 | $minitem = $items[$i]; | ||||
| 167 | 15 | 20 | foreach my $j (0 .. $i - 1) { | ||||
| 168 | 24 24 | 14 21 | unshift @{$arrays[$j]}, $items[$j]; | ||||
| 169 | 24 | 21 | $items[$j] = undef; | ||||
| 170 | } | ||||||
| 171 | } elsif ($cmp < 0) { | ||||||
| 172 | 36 36 | 19 34 | unshift @{$arrays[$i]}, $items[$i]; | ||||
| 173 | 36 | 33 | $items[$i] = undef; | ||||
| 174 | } | ||||||
| 175 | } else { | ||||||
| 176 | 162 | 178 | $minitem = $items[$i] if defined $items[$i]; | ||||
| 177 | } | ||||||
| 178 | } | ||||||
| 179 | 108 | 132 | return @items; | ||||
| 180 | } | ||||||
| 181 | |||||||
| 182 | # Compares the versions taking into account some oddities like the fact | ||||||
| 183 | # that we want backport versions to sort higher than the version | ||||||
| 184 | # on which they are based. | ||||||
| 185 | sub compare_versions { | ||||||
| 186 | 162 | 104 | my ($a, $b) = @_; | ||||
| 187 | |||||||
| 188 | 162 | 119 | return 0 if not defined $a and not defined $b; | ||||
| 189 | 162 | 119 | return 1 if not defined $b; | ||||
| 190 | 162 | 100 | return -1 if not defined $a; | ||||
| 191 | |||||||
| 192 | 162 | 105 | my ($av, $bv) = ($a, $b); | ||||
| 193 | |||||||
| 194 | 162 | 495 | $av = $a->get_version() if ref $a and $a->isa('Dpkg::Changelog::Entry'); | ||||
| 195 | 162 | 325 | $bv = $b->get_version() if ref $b and $b->isa('Dpkg::Changelog::Entry'); | ||||
| 196 | |||||||
| 197 | 162 | 129 | if ($merge_unreleased) { | ||||
| 198 | 12 | 13 | return 0 if $a->get_distributions() eq 'UNRELEASED' and | ||||
| 199 | $b->get_distributions() eq 'UNRELEASED'; | ||||||
| 200 | } | ||||||
| 201 | # Backports are not real prereleases. | ||||||
| 202 | 159 | 105 | if (defined $backport_version_regex) { | ||||
| 203 | 159 | 280 | $a =~ s/$backport_version_regex/+$1/; | ||||
| 204 | 159 | 190 | $b =~ s/$backport_version_regex/+$1/; | ||||
| 205 | } | ||||||
| 206 | 159 | 127 | if ($merge_prereleases) { | ||||
| 207 | 72 | 68 | $av =~ s/~[^~]*$//; | ||||
| 208 | 72 | 53 | $bv =~ s/~[^~]*$//; | ||||
| 209 | } | ||||||
| 210 | 159 | 185 | $av = Dpkg::Version->new($av); | ||||
| 211 | 159 | 143 | $bv = Dpkg::Version->new($bv); | ||||
| 212 | 159 | 138 | return $av <=> $bv; | ||||
| 213 | } | ||||||
| 214 | |||||||
| 215 | # Merge changelog entries smartly by merging individually the different | ||||||
| 216 | # parts constituting an entry | ||||||
| 217 | sub merge_entries($$$) { | ||||||
| 218 | 30 | 26 | my ($o, $a, $b) = @_; | ||||
| 219 | # NOTE: Only $o can be undef | ||||||
| 220 | |||||||
| 221 | # Merge the trailer line | ||||||
| 222 | 30 | 35 | unless (merge_entry_item('blank_after_trailer', $o, $a, $b)) { | ||||
| 223 | 0 | 0 | unshift @result, ''; | ||||
| 224 | } | ||||||
| 225 | 30 | 89 | unless (merge_entry_item('trailer', $o, $a, $b)) { | ||||
| 226 | 9 | 10 | merge_conflict($a->get_part('trailer'), $b->get_part('trailer')); | ||||
| 227 | } | ||||||
| 228 | |||||||
| 229 | # Merge the changes | ||||||
| 230 | 30 | 62 | unless (merge_entry_item('blank_after_changes', $o, $a, $b)) { | ||||
| 231 | 0 | 0 | unshift @result, ''; | ||||
| 232 | } | ||||||
| 233 | my @merged = merge(defined $o ? $o->get_part('changes') : [], | ||||||
| 234 | $a->get_part('changes'), $b->get_part('changes'), | ||||||
| 235 | { | ||||||
| 236 | CONFLICT => sub { | ||||||
| 237 | 9 | 3364 | my ($ca, $cb) = @_; | ||||
| 238 | 9 | 7 | $exitcode = 1; | ||||
| 239 | 9 | 12 | return get_conflict_block($ca, $cb); | ||||
| 240 | } | ||||||
| 241 | 30 | 50 | }); | ||||
| 242 | 30 | 19750 | unshift @result, @merged; | ||||
| 243 | |||||||
| 244 | # Merge the header line | ||||||
| 245 | 30 | 41 | unless (merge_entry_item('blank_after_header', $o, $a, $b)) { | ||||
| 246 | 0 | 0 | unshift @result, ''; | ||||
| 247 | } | ||||||
| 248 | 30 | 35 | unless (merge_entry_item('header', $o, $a, $b)) { | ||||
| 249 | 12 | 23 | merge_conflict($a->get_part('header'), $b->get_part('header')); | ||||
| 250 | } | ||||||
| 251 | } | ||||||
| 252 | |||||||
| 253 | sub join_lines($) { | ||||||
| 254 | 627 | 282 | my $array = shift; | ||||
| 255 | 627 | 554 | return join("\n", @$array) if ref($array) eq 'ARRAY'; | ||||
| 256 | 375 | 202 | return $array; | ||||
| 257 | } | ||||||
| 258 | |||||||
| 259 | # Try to merge the obvious cases, return 1 on success and 0 on failure | ||||||
| 260 | # O A B | ||||||
| 261 | # - x x => x | ||||||
| 262 | # o o b => b | ||||||
| 263 | # - - b => b | ||||||
| 264 | # o a o => a | ||||||
| 265 | # - a - => a | ||||||
| 266 | sub merge_block($$$;&) { | ||||||
| 267 | 258 | 199 | my ($o, $a, $b, $preprocess) = @_; | ||||
| 268 | 258 | 476 | $preprocess //= \&join_lines; | ||||
| 269 | 258 | 239 | $o = $preprocess->($o) if defined $o; | ||||
| 270 | 258 | 229 | $a = $preprocess->($a) if defined $a; | ||||
| 271 | 258 | 224 | $b = $preprocess->($b) if defined $b; | ||||
| 272 | 258 | 258 | return 1 if not defined($a) and not defined($b); | ||||
| 273 | 246 | 867 | if (defined($a) and defined($b) and ($a eq $b)) { | ||||
| 274 | 144 | 151 | unshift @result, $a; | ||||
| 275 | } elsif ((defined($a) and defined($o) and ($a eq $o)) or | ||||||
| 276 | (not defined($a) and not defined($o))) { | ||||||
| 277 | 18 | 31 | unshift @result, $b if defined $b; | ||||
| 278 | } elsif ((defined($b) and defined($o) and ($b eq $o)) or | ||||||
| 279 | (not defined($b) and not defined($o))) { | ||||||
| 280 | 33 | 35 | unshift @result, $a if defined $a; | ||||
| 281 | } else { | ||||||
| 282 | 51 | 71 | return 0; | ||||
| 283 | } | ||||||
| 284 | 195 | 265 | return 1; | ||||
| 285 | } | ||||||
| 286 | |||||||
| 287 | sub merge_entry_item($$$$) { | ||||||
| 288 | 150 | 142 | my ($item, $o, $a, $b) = @_; | ||||
| 289 | 150 | 407 | if (blessed($o) and $o->isa('Dpkg::Changelog::Entry')) { | ||||
| 290 | 120 | 127 | $o = $o->get_part($item); | ||||
| 291 | } elsif (ref $o) { | ||||||
| 292 | 0 | 0 | $o = $o->{$item}; | ||||
| 293 | } | ||||||
| 294 | 150 | 373 | if (blessed($a) and $a->isa('Dpkg::Changelog::Entry')) { | ||||
| 295 | 150 | 118 | $a = $a->get_part($item); | ||||
| 296 | } elsif (ref $a) { | ||||||
| 297 | 0 | 0 | $a = $a->{$item}; | ||||
| 298 | } | ||||||
| 299 | 150 | 342 | if (blessed($b) and $b->isa('Dpkg::Changelog::Entry')) { | ||||
| 300 | 150 | 106 | $b = $b->get_part($item); | ||||
| 301 | } elsif (ref $b) { | ||||||
| 302 | 0 | 0 | $b = $b->{$item}; | ||||
| 303 | } | ||||||
| 304 | 150 | 128 | return merge_block($o, $a, $b); | ||||
| 305 | } | ||||||
| 306 | |||||||
| 307 | sub merge_conflict($$) { | ||||||
| 308 | 21 | 17 | my ($a, $b) = @_; | ||||
| 309 | 21 | 32 | unshift @result, get_conflict_block($a, $b); | ||||
| 310 | 21 | 27 | $exitcode = 1; | ||||
| 311 | } | ||||||
| 312 | |||||||
| 313 | sub get_conflict_block($$) { | ||||||
| 314 | 30 | 24 | my ($a, $b) = @_; | ||||
| 315 | 30 | 20 | my (@a, @b); | ||||
| 316 | 30 | 36 | push @a, $a if defined $a; | ||||
| 317 | 30 | 25 | push @b, $b if defined $b; | ||||
| 318 | 30 9 | 35 10 | @a = @{$a} if ref($a) eq 'ARRAY'; | ||||
| 319 | 30 9 | 30 9 | @b = @{$b} if ref($b) eq 'ARRAY'; | ||||
| 320 | 30 | 68 | return ('<<<<<<<', @a, '=======', @b, '>>>>>>>'); | ||||
| 321 | } | ||||||