dpkg-mergechangelogs.pl 8.4 KB

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