ExtractConfigComparator.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. # ------------------------------------------------------------------------------
  2. # (C) Crown copyright Met Office. All rights reserved.
  3. # For further details please refer to the file COPYRIGHT.txt
  4. # which you should have received as part of this distribution.
  5. # ------------------------------------------------------------------------------
  6. use strict;
  7. use warnings;
  8. ################################################################################
  9. # A generic reporter of the comparator's result
  10. {
  11. package Reporter;
  12. ############################################################################
  13. # Class method: Constructor
  14. sub new {
  15. my ($class) = @_;
  16. return bless(\do{my $annon_scalar}, $class);
  17. }
  18. ############################################################################
  19. # Class method: Factory for Reporter object
  20. sub get_reporter {
  21. my ($self, $comparator) = @_;
  22. my $class = defined($comparator->get_wiki()) ? 'WikiReporter'
  23. : 'TextReporter'
  24. ;
  25. return $class->new();
  26. }
  27. ############################################################################
  28. # Reports the results
  29. sub report {
  30. my ($self, $comparator) = @_;
  31. if (keys(%{$comparator->get_log_of()})) {
  32. print("Revisions at which extract declarations are modified:\n\n");
  33. }
  34. $self->report_impl($comparator);
  35. }
  36. ############################################################################
  37. # Does the actual reporting
  38. sub report_impl {
  39. my ($self, $comparator) = @_;
  40. }
  41. }
  42. ################################################################################
  43. # Reports the comparator's result in Trac wiki format
  44. {
  45. package WikiReporter;
  46. our @ISA = qw{Reporter};
  47. use Fcm::CmUrl;
  48. use Fcm::Keyword;
  49. use Fcm::Util qw{tidy_url};
  50. ############################################################################
  51. # Reports the comparator's result
  52. sub report_impl {
  53. my ($self, $comparator) = @_;
  54. # Output in wiki format
  55. my $wiki_url = Fcm::CmUrl->new(
  56. URL => tidy_url(Fcm::Keyword::expand($comparator->get_wiki()))
  57. );
  58. my $base_trac
  59. = $comparator->get_wiki()
  60. ? Fcm::Keyword::get_browser_url($wiki_url->project_url())
  61. : $wiki_url;
  62. if (!$base_trac) {
  63. $base_trac = $wiki_url;
  64. }
  65. for my $key (sort keys(%{$comparator->get_log_of()})) {
  66. my $branch_trac = Fcm::Keyword::get_browser_url($key);
  67. $branch_trac =~ s{\A $base_trac (?:/*|\z)}{source:}xms;
  68. print("[$branch_trac]:\n");
  69. my %branch_of = %{$comparator->get_log_of()->{$key}};
  70. for my $rev (sort {$b <=> $a} keys(%branch_of)) {
  71. print(
  72. $branch_of{$rev}->display_svnlog($rev, $base_trac), "\n",
  73. );
  74. }
  75. print("\n");
  76. }
  77. }
  78. }
  79. ################################################################################
  80. # Reports the comparator's result in simple text format
  81. {
  82. package TextReporter;
  83. our @ISA = qw{Reporter};
  84. use Fcm::Config;
  85. my $SEPARATOR = q{-} x 80 . "\n";
  86. ############################################################################
  87. # Reports the comparator's result
  88. sub report_impl {
  89. my ($self, $comparator) = @_;
  90. for my $key (sort keys(%{$comparator->get_log_of()})) {
  91. # Output in plain text format
  92. print $key, ':', "\n";
  93. my %branch_of = %{$comparator->get_log_of()->{$key}};
  94. if (Fcm::Config->instance()->verbose() > 1) {
  95. for my $rev (sort {$b <=> $a} keys(%branch_of)) {
  96. print(
  97. $SEPARATOR, $branch_of{$rev}->display_svnlog($rev), "\n"
  98. );
  99. }
  100. }
  101. else {
  102. print(join(q{ }, sort {$b <=> $a} keys(%branch_of)), "\n");
  103. }
  104. print $SEPARATOR, "\n";
  105. }
  106. }
  107. }
  108. package Fcm::ExtractConfigComparator;
  109. use Fcm::CmUrl;
  110. use Fcm::Extract;
  111. ################################################################################
  112. # Class method: Constructor
  113. sub new {
  114. my ($class, $args_ref) = @_;
  115. return bless({%{$args_ref}}, $class);
  116. }
  117. ################################################################################
  118. # Returns an array containing the 2 configuration files to compare
  119. sub get_files {
  120. my ($self) = @_;
  121. return (wantarray() ? @{$self->{files}} : $self->{files});
  122. }
  123. ################################################################################
  124. # Returns the wiki link on wiki mode
  125. sub get_wiki {
  126. my ($self) = @_;
  127. return $self->{wiki};
  128. }
  129. ################################################################################
  130. # Returns the result log
  131. sub get_log_of {
  132. my ($self) = @_;
  133. return (wantarray() ? %{$self->{log_of}} : $self->{log_of});
  134. }
  135. ################################################################################
  136. # Invokes the comparator
  137. sub invoke {
  138. my ($self) = @_;
  139. # Reads the extract configurations
  140. my (@cfg, $rc);
  141. for my $i (0 .. 1) {
  142. $cfg[$i] = Fcm::Extract->new();
  143. $cfg[$i]->cfg()->src($self->get_files()->[$i]);
  144. $cfg[$i]->parse_cfg();
  145. $rc = $cfg[$i]->expand_cfg();
  146. if (!$rc) {
  147. e_report();
  148. }
  149. }
  150. # Get list of URLs
  151. # --------------------------------------------------------------------------
  152. my @urls = ();
  153. for my $i (0 .. 1) {
  154. # List of branches in each extract configuration file
  155. my @branches = @{$cfg[$i]->branches()};
  156. BRANCH:
  157. for my $branch (@branches) {
  158. # Ignore declarations of local directories
  159. if ($branch->type() eq 'user') {
  160. next BRANCH;
  161. }
  162. # List of SRC declarations in each branch
  163. my %dirs = %{$branch->dirs()};
  164. for my $dir (values(%dirs)) {
  165. # Set up a new instance of Fcm::CmUrl object for each SRC
  166. my $cm_url = Fcm::CmUrl->new (
  167. URL => $dir . (
  168. $branch->revision() ? '@' . $branch->revision() : q{}
  169. ),
  170. );
  171. $urls[$i]{$cm_url->branch_url()}{$dir} = $cm_url;
  172. }
  173. }
  174. }
  175. # Compare
  176. # --------------------------------------------------------------------------
  177. $self->{log_of} = {};
  178. for my $i (0 .. 1) {
  179. # Compare the first file with the second one and then vice versa
  180. my $j = ($i == 0) ? 1 : 0;
  181. for my $branch (sort keys(%{$urls[$i]})) {
  182. if (exists($urls[$j]{$branch})) {
  183. # Same REPOS declarations in both files
  184. DIR:
  185. for my $dir (sort keys(%{$urls[$i]{$branch}})) {
  186. if (exists($urls[$j]{$branch}{$dir})) {
  187. if ($i == 1) {
  188. next DIR;
  189. }
  190. my $this_url = $urls[$i]{$branch}{$dir};
  191. my $that_url = $urls[$j]{$branch}{$dir};
  192. # Compare their last changed revisions
  193. my $this_rev
  194. = $this_url->svninfo(FLAG => 'Last Changed Rev');
  195. my $that_rev
  196. = $that_url->svninfo(FLAG => 'Last Changed Rev');
  197. # Make sure last changed revisions differ
  198. if ($this_rev eq $that_rev) {
  199. next DIR;
  200. }
  201. # Not interested in the log before the minimum revision
  202. my $min_rev
  203. = $this_url->pegrev() > $that_url->pegrev()
  204. ? $that_url->pegrev() : $this_url->pegrev();
  205. $this_rev = $min_rev if $this_rev < $min_rev;
  206. $that_rev = $min_rev if $that_rev < $min_rev;
  207. # Get list of changed revisions using the commit log
  208. my $u = ($this_rev > $that_rev) ? $this_url : $that_url;
  209. my %revs = $u->svnlog(REV => [$this_rev, $that_rev]);
  210. REV:
  211. for my $rev (keys %revs) {
  212. # Check if revision is already in the list
  213. if (
  214. exists($self->{log_of}{$branch}{$rev})
  215. || $rev == $min_rev
  216. ) {
  217. next REV;
  218. }
  219. # Get list of changed paths. Accept this revision
  220. # only if it contains changes in the current branch
  221. my %paths = %{$revs{$rev}{paths}};
  222. PATH:
  223. for my $path (keys(%paths)) {
  224. my $change_url
  225. = Fcm::CmUrl->new(URL => $u->root() . $path);
  226. if ($change_url->branch() eq $u->branch()) {
  227. $self->{log_of}{$branch}{$rev} = $u;
  228. last PATH;
  229. }
  230. }
  231. }
  232. }
  233. else {
  234. $self->_report_added(
  235. $urls[$i]{$branch}{$dir}->url_peg(), $i, $j);
  236. }
  237. }
  238. }
  239. else {
  240. $self->_report_added($branch, $i, $j);
  241. }
  242. }
  243. }
  244. my $reporter = Reporter->get_reporter($self);
  245. $reporter->report($self);
  246. return $rc;
  247. }
  248. ################################################################################
  249. # Reports added/deleted declaration
  250. sub _report_added {
  251. my ($self, $branch, $i, $j) = @_;
  252. printf(
  253. "%s:\n in : %s\n not in: %s\n\n",
  254. $branch, $self->get_files()->[$i], $self->get_files()->[$j],
  255. );
  256. }
  257. 1;
  258. __END__
  259. =head1 NAME
  260. Fcm::ExtractConfigComparator
  261. =head1 SYNOPSIS
  262. use Fcm::ExtractConfigComparator;
  263. my $comparator = Fcm::ExtractConfigComparator->new({files => \@files});
  264. $comparator->invoke();
  265. =head1 DESCRIPTION
  266. An object of this class represents a comparator of FCM extract configuration.
  267. It is used to compare the VC branch declarations in 2 FCM extract configuration
  268. files.
  269. =head1 METHODS
  270. =over 4
  271. =item C<new({files =E<gt> \@files, wiki =E<gt> $wiki})>
  272. Constructor.
  273. =item get_files()
  274. Returns an array containing the 2 configuration files to compare.
  275. =item get_wiki()
  276. Returns the wiki link on wiki mode.
  277. =item invoke()
  278. Invokes the comparator.
  279. =back
  280. =head1 TO DO
  281. More documentation.
  282. Improve the parser for extract configuration.
  283. Separate the comparator with the reporters.
  284. Add reporter to display HTML.
  285. More unit tests.
  286. =head1 SEE ALSO
  287. L<Fcm::Extract|Fcm::Extract>
  288. =head1 COPYRIGHT
  289. E<169> Crown copyright Met Office. All rights reserved.
  290. =cut