source: OFFICIAL/FCM_V1.3/lib/Fcm/ExtractFile.pm

Last change on this file was 1, checked in by fcm, 15 years ago

creation de larborescence

File size: 11.4 KB
Line 
1#!/usr/bin/perl
2# ------------------------------------------------------------------------------
3# NAME
4#   Fcm::ExtractFile
5#
6# DESCRIPTION
7#   Select/combine a file in different branches and extract it to destination.
8#
9# COPYRIGHT
10#   (C) Crown copyright Met Office. All rights reserved.
11#   For further details please refer to the file COPYRIGHT.txt
12#   which you should have received as part of this distribution.
13# ------------------------------------------------------------------------------
14
15package Fcm::ExtractFile;
16@ISA = qw(Fcm::Base);
17
18# Standard pragma
19use warnings;
20use strict;
21
22# Standard modules
23use File::Basename;
24use File::Compare;
25use File::Copy;
26use File::Path;
27use File::Spec;
28use File::Temp qw(tempfile);
29
30# FCM component modules
31use Fcm::Base;
32use Fcm::Util;
33
34# List of property methods for this class
35my @scalar_properties = (
36  'conflict',    # conflict mode
37  'dest',        # search path to destination file
38  'dest_status', # destination status, see below
39  'pkgname',     # package name of this file
40  'src',         # list of Fcm::ExtractSrc, specified for this file
41  'src_actual',  # list of Fcm::ExtractSrc, actually used by this file
42  'src_status',  # source status, see below
43);
44
45# Status code definition for $self->dest_status
46our %DEST_STATUS_CODE = (
47  ''  => 'unchanged',
48  'M' => 'modified',
49  'A' => 'added',
50  'a' => 'added, overridding inherited',
51  'D' => 'deleted',
52  'd' => 'deleted, overridding inherited',
53  '?' => 'irrelevant',
54);
55
56# Status code definition for $self->src_status
57our %SRC_STATUS_CODE = (
58  'A' => 'added by a branch',
59  'B' => 'from the base',
60  'D' => 'deleted by a branch',
61  'M' => 'modified by a branch',
62  'G' => 'merged from 2+ branches',
63  'O' => 'overridden by a branch',
64  '?' => 'irrelevant',
65);
66
67# ------------------------------------------------------------------------------
68# SYNOPSIS
69#   $obj = Fcm::ExtractFile->new ();
70#
71# DESCRIPTION
72#   This method constructs a new instance of the Fcm::ExtractFile class.
73# ------------------------------------------------------------------------------
74
75sub new {
76  my $this  = shift;
77  my %args  = @_;
78  my $class = ref $this || $this;
79
80  my $self = Fcm::Base->new (%args);
81
82  for (@scalar_properties) {
83    $self->{$_} = exists $args{$_} ? $args{$_} : undef;
84  }
85
86  bless $self, $class;
87  return $self;
88}
89
90# ------------------------------------------------------------------------------
91# SYNOPSIS
92#   $value = $obj->X;
93#   $obj->X ($value);
94#
95# DESCRIPTION
96#   Details of these properties are explained in @scalar_properties.
97# ------------------------------------------------------------------------------
98
99for my $name (@scalar_properties) {
100  no strict 'refs';
101
102  *$name = sub {
103    my $self = shift;
104
105    # Argument specified, set property to specified argument
106    if (@_) {
107      $self->{$name} = $_[0];
108    }
109
110    # Default value for property
111    if (not defined $self->{$name}) {
112      if ($name eq 'conflict') {
113        $self->{$name} = 'merge'; # default to "merge" mode
114
115      } elsif ($name eq 'dest' or $name eq 'src' or $name eq 'src_actual') {
116        $self->{$name} = [];      # default to an empty list
117      }
118    }
119
120    return $self->{$name};
121  }
122}
123
124# ------------------------------------------------------------------------------
125# SYNOPSIS
126#   $rc = $obj->run();
127#
128# DESCRIPTION
129#   This method runs only if $self->dest_status is not defined. It updates the
130#   destination according to the source in the list and the conflict mode
131#   setting. It updates the file in $self->dest as appropriate and sets
132#   $self->dest_status. (See above.) This method returns true on success.
133# ------------------------------------------------------------------------------
134
135sub run {
136  my ($self) = @_;
137  my $rc = 1;
138
139  if (not defined ($self->dest_status)) {
140    # Assume file unchanged
141    $self->dest_status ('');
142
143    if (@{ $self->src }) {
144      my $used;
145      # Determine or set up a file for comparing with the destination
146      ($rc, $used) = $self->run_get_used();
147
148      # Attempt to compare the destination with $used. Update on change.
149      if ($rc) {
150        $rc = defined ($used) ? $self->run_update($used) : $self->run_delete();
151      }
152
153    } else {
154      # No source, delete file in destination
155      $self->src_status ('?');
156      $rc = $self->run_delete();
157    }
158  }
159
160  return $rc;
161}
162
163# ------------------------------------------------------------------------------
164# SYNOPSIS
165#   $rc = $obj->run_delete();
166#
167# DESCRIPTION
168#   This method is part of run(). It detects this file in the destination path.
169#   If this file is in the current destination, it attempts to delete it and
170#   sets the dest_status to "D". If this file is in an inherited destination,
171#   it sets the dest_status to "d".
172# ------------------------------------------------------------------------------
173
174sub run_delete {
175  my ($self) = @_;
176
177  my $rc = 1;
178
179  $self->dest_status ('?');
180  for my $i (0 .. @{ $self->dest } - 1) {
181    my $dest = File::Spec->catfile ($self->dest->[$i], $self->pkgname);
182    next unless -f $dest;
183    if ($i == 0) {
184      $rc = unlink $dest;
185      $self->dest_status ('D');
186
187    } else {
188      $self->dest_status ('d');
189      last;
190    }
191  }
192
193  return $rc;
194}
195
196# ------------------------------------------------------------------------------
197# SYNOPSIS
198#   ($rc, $used) = $obj->run_get_used();
199#
200# DESCRIPTION
201#   This method is part of run(). It attempts to work out or set up the $used
202#   file. ($used is undef if it is not defined in a branch for this file.)
203# ------------------------------------------------------------------------------
204
205sub run_get_used {
206  my ($self) = @_;
207  my $rc = 1;
208  my $used;
209
210  my @sources = ($self->src->[0]);
211  my $src_status = 'B';
212  if (defined ($self->src->[0]->cache)) {
213    # File exists in base branch
214    for my $i (1 .. @{ $self->src } - 1) {
215      if (defined ($self->src->[$i]->cache)) {
216        # Detect changes in this file between base branch and branch $i
217        push @sources, $self->src->[$i]
218          if &compare ($self->src->[0]->cache, $self->src->[$i]->cache);
219
220      } else {
221        # File deleted in branch $i
222        @sources = ($self->src->[$i]);
223        last unless $self->conflict eq 'override';
224      }
225    }
226
227    if ($rc) {
228      if (@sources > 2) {
229        if ($self->conflict eq 'fail') {
230          # On conflict, fail in fail mode
231          w_report 'ERROR: ', $self->pkgname,
232                   ': modified in 2+ branches in fail conflict mode.';
233          $rc = undef;
234
235        } elsif ($self->conflict eq 'override') {
236          $used = $sources[-1]->cache;
237          $src_status = 'O';
238
239        } else {
240          # On conflict, attempt to merge in merge mode
241          ($rc, $used) = $self->run_get_used_by_merge (@sources);
242          $src_status = 'G' if $rc;
243        }
244
245      } else {
246        # 0 or 1 change, use last source
247        if (defined $sources[-1]->cache) {
248          $used = $sources[-1]->cache;
249          $src_status = 'M' if @sources > 1;
250
251        } else {
252          $src_status = 'D';
253        }
254      }
255    }
256
257  } else {
258    # File does not exist in base branch
259    @sources = ($self->src->[-1]);
260    $used = $self->src->[1]->cache;
261    $src_status = (defined ($used) ? 'A' : 'D');
262    if ($self->conflict ne 'override' and defined ($used)) {
263      for my $i (1 - @{ $self->src } .. -2) {
264        # Allow this only if files are the same in all branches
265        my $file = $self->src->[$i]->cache;
266        if ((not defined ($file)) or &compare ($used, $file)) {
267          w_report 'ERROR: ', $self->pkgname, ': cannot merge:',
268                   ' not found in base branch,',
269                   ' but differs in subsequent branches.';
270          $rc = undef;
271          last;
272
273        } else {
274          unshift @sources, $self->src->[$i];
275        }
276      }
277    }
278  }
279
280  $self->src_status ($src_status);
281  $self->src_actual (\@sources);
282
283  return ($rc, $used);
284}
285
286# ------------------------------------------------------------------------------
287# SYNOPSIS
288#   ($rc, $used) = $obj->run_get_used_by_merge(@soruces);
289#
290# DESCRIPTION
291#   This method is part of run_get_used(). It attempts to merge the files in
292#   @sources and return a temporary file $used. @sources should be an array of
293#   Fcm::ExtractSrc objects. On success, $rc will be set to true.
294# ------------------------------------------------------------------------------
295
296sub run_get_used_by_merge {
297  my ($self, @sources) = @_;
298  my $rc = 1;
299
300  # Get temporary file
301  my ($fh, $used) = &tempfile ('fcm.ext.merge.XXXXXX', UNLINK => 1);
302  close $fh or die $used, ': cannot close';
303
304  for my $i (2 .. @sources - 1) {
305    # Invoke the diff3 command to merge
306    my $mine = ($i == 2 ? $sources[1]->cache : $used);
307    my $older = $sources[0]->cache;
308    my $yours = $sources[$i]->cache;
309    my @command = (
310      $self->setting (qw/TOOL DIFF3/),
311      split (/\s+/, $self->setting (qw/TOOL DIFF3FLAGS/)),
312      $mine, $older, $yours,
313    );
314    my $code;
315    my @out = &run_command (
316      \@command,
317      METHOD => 'qx',
318      ERROR  => 'ignore',
319      PRINT  => $self->verbose > 1,
320      RC     => \$code,
321      TIME   => $self->verbose > 2,
322    );
323
324    if ($code) {
325      # Failure, report and return
326      my $m = ($code == 1)
327              ? 'cannot resolve conflicts:'
328              : $self->setting (qw/TOOL DIFF3/) . 'command failed';
329      w_report 'ERROR: ', $self->pkgname, ': merge - ', $m;
330      if ($code == 1 and $self->verbose) {
331        for (0 .. $i) {
332          my $src = $sources[$_]->uri eq $sources[$_]->cache
333                    ? $sources[$_]->cache
334                    : ($sources[$_]->uri . '@' . $sources[$_]->rev);
335          w_report '  source[', $_, ']=', $src;
336        }
337
338        for (0 .. $i) {
339          w_report '  cache', $_, '=', $sources[$_]->cache;
340        }
341
342        w_report @out if $self->verbose > 2;
343      }
344      $rc = undef;
345      last;
346
347    } else {
348      # Success, write result to temporary file
349      open FILE, '>', $used or die $used, ': cannot open (', $!, ')';
350      print FILE @out;
351      close FILE or die $used, ': cannot close (', $!, ')';
352
353      # File permission, use most permissive combination of $mine and $yours
354      my $perm = ((stat($mine))[2] & 07777) | ((stat($yours))[2] & 07777);
355      chmod ($perm, $used);
356    }
357  }
358
359  return ($rc, $used);
360}
361
362# ------------------------------------------------------------------------------
363# SYNOPSIS
364#   $rc = $obj->run_update($used);
365#
366# DESCRIPTION
367#   This method is part of run(). It compares the $used file with the one in
368#   the destination. If the file does not exist in the destination or if its
369#   content is out of date, the destination is updated with the content in the
370#   $used file.
371# ------------------------------------------------------------------------------
372
373sub run_update {
374  my ($self, $used) = @_;
375
376  my $is_in_dest;
377  my $is_diff = 1;
378  my $rc = 1;
379
380  # Compare with current destination
381  for my $i (0 .. @{ $self->dest } - 1) {
382    my $dest = File::Spec->catfile ($self->dest->[$i], $self->pkgname);
383    next unless -f $dest;
384    $is_in_dest = $i;
385    $is_diff = &compare($used, $dest);
386    last;
387  }
388
389  # Update destination only on change
390  if ($is_diff) {
391    my $dest = File::Spec->catfile ($self->dest->[0], $self->pkgname);
392    my $dir  = &dirname ($dest);
393    mkpath $dir if (not -d $dir);
394    $rc = &copy ($used, $dest);
395    chmod (((stat($used))[2] & 07777), $dest);
396
397    # Set dest_status
398    if ($rc and defined ($is_in_dest)) {
399      $self->dest_status ($is_in_dest ? 'a' : 'M');
400
401    } else {
402      $self->dest_status ('A');
403    }
404  }
405
406  return $rc;
407}
408
409# ------------------------------------------------------------------------------
410
4111;
412
413__END__
Note: See TracBrowser for help on using the repository browser.