New URL for NEMO forge!   http://forge.nemo-ocean.eu

Since March 2022 along with NEMO 4.2 release, the code development moved to a self-hosted GitLab.
This present forge is now archived and remained online for history.
ResolveConflicts.pm in vendors/lib/FCM/System/CM – NEMO

source: vendors/lib/FCM/System/CM/ResolveConflicts.pm @ 10669

Last change on this file since 10669 was 10669, checked in by nicolasmartin, 5 years ago

Import latest FCM release from Github into the repository for testing

File size: 23.3 KB
Line 
1#-------------------------------------------------------------------------------
2# (C) British Crown Copyright 2006-17 Met Office.
3#
4# This file is part of FCM, tools for managing and building source code.
5#
6# FCM 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 3 of the License, or
9# (at your option) any later version.
10#
11# FCM 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 FCM. If not, see <http://www.gnu.org/licenses/>.
18#-------------------------------------------------------------------------------
19use strict;
20use warnings;
21
22#-------------------------------------------------------------------------------
23package FCM::System::CM::ResolveConflicts;
24use base qw{Exporter};
25our @EXPORT_OK = qw{_cm_resolve_conflicts};
26
27use Cwd qw{cwd};
28use FCM::Context::Event;
29use FCM::System::Exception;
30use File::Basename qw{basename dirname};
31use File::Copy qw{copy};
32use File::Spec::Functions qw{abs2rel catfile rel2abs};
33use File::Temp;
34
35# LxIy stands for local x, incoming y in the tree conflict description.
36# The letters of x and y correspond to:
37# A => add,
38# D => delete,
39# E => edit,
40# M => missing,
41# P => replace,
42# R => rename,
43# although the 'rename' has to be detected by our code below.
44
45our %TREE_CONFLICT_GET_GRAPHIC_SOURCES_FUNC_FOR = (
46    LEIR => \&_cm_tree_conflict_get_graphic_sources_for_leir,
47    LRIE => \&_cm_tree_conflict_get_graphic_sources_for_lrie,
48    LRIR => \&_cm_tree_conflict_get_graphic_sources_for_lrir,
49);
50
51# A tree conflict key must be present here for auto-resolving.
52our %TREE_CONFLICT_GET_FINAL_ACTIONS_FUNC_FOR = (
53    LAIA => \&_cm_tree_conflict_get_actions_for_laia,
54    LDID => sub {},
55    LDIE => \&_cm_tree_conflict_get_actions_for_ldie,
56    LDIR => \&_cm_tree_conflict_get_actions_for_ldir,
57    LEID => \&_cm_tree_conflict_get_actions_for_leid,
58    LEIR => \&_cm_tree_conflict_get_actions_for_leir,
59    LEIP => \&_cm_tree_conflict_get_actions_for_leip,
60    LRID => \&_cm_tree_conflict_get_actions_for_lrid,
61    LRIE => \&_cm_tree_conflict_get_actions_for_lrie,
62    LRIR => \&_cm_tree_conflict_get_actions_for_lrir,
63);
64
65# Handle aliases for actions.
66our %TREE_CONFLICT_GET_UNALIAS_FOR = (
67    'obstruction' => 'add',
68    'missing' => 'delete',
69);
70
71# Number of renamed files that triggers a time warning.
72our $TREE_CONFLICT_WARN_FILES_THRESHOLD = 10;
73
74my $E = 'FCM::System::Exception';
75# Regular expressions
76my %RE = (
77    # determines if a file was copied, i.e. added with history from "svn status"
78    ST_COPIED => qr{^A..\+....(.*)}msx,
79);
80
81# Resolve conflicts.
82sub _cm_resolve_conflicts {
83    my ($attrib_ref, $option_ref, @args) = @_;
84    my $UTIL = $attrib_ref->{util};
85    my $pwd = cwd();
86    if (!@args) {
87        push(@args, '.');
88    }
89    for my $arg (@args) {
90        if (!-e $arg) {
91            die("$arg: $!\n");
92        }
93        chdir($attrib_ref->{svn}->get_wc_root($arg)) || die("$arg: $!\n");
94        my @command = qw{svn status};
95        my %value_of = %{$UTIL->shell_simple(\@command)};
96        if ($value_of{rc}) {
97            return $E->throw($E->SHELL, {command_list => \@command, %value_of});
98        }
99        my @status_lines = grep {$_} split("\n", $value_of{o});
100        local(%ENV) = %ENV;
101        $ENV{FCM_GRAPHIC_MERGE} ||= $UTIL->external_cfg_get('graphic-merge');
102        for my $path (map {($_ =~ qr{\AC.{6}\s(.*)\z}msx)} @status_lines) {
103            _cm_resolve_text_conflict(
104                $attrib_ref,
105                $option_ref,
106                $path,
107                @status_lines,
108            );
109        }
110        for my $path (map {($_ =~ qr{\A.{6}C\s(.*)\z}msx)} @status_lines) {
111            _cm_resolve_tree_conflict(
112                $attrib_ref,
113                $option_ref,
114                $path,
115                @status_lines,
116            );
117        }
118    }
119    chdir($pwd);
120}
121
122# Helper for _cm_resolve_conflicts, launch graphic merge tool.
123sub _cm_graphic_merge {
124    my $attrib_ref = shift();
125    my @command = ('fcm_graphic_merge', @_);
126    my $UTIL = $attrib_ref->{util};
127    my %value_of = %{$UTIL->shell_simple(\@command)};
128    # rc==0: all conflicts resovled
129    # rc==1: some conflicts not resolved
130    # rc==2: trouble
131    if (!grep {$_ eq $value_of{rc}} (0, 1)) {
132        return $E->throw(
133            $E->SHELL, {command_list => \@command, %value_of}, $value_of{e},
134        );
135    }
136    $UTIL->event(FCM::Context::Event->OUT, $value_of{o});
137    $value_of{rc};
138}
139
140# Resolve a text conflict.
141sub _cm_resolve_text_conflict {
142    my ($attrib_ref, $option_ref, $path) = @_;
143    my $PROMPT = $attrib_ref->{prompt};
144    my $UTIL = $attrib_ref->{util};
145    if (-B $path) {
146        $UTIL->event(FCM::Context::Event->CM_CONFLICT_TEXT_SKIP, $path);
147        return;
148    }
149    $UTIL->event(FCM::Context::Event->CM_CONFLICT_TEXT, $path);
150
151    # Get conflicts markers files
152    my %info = %{$attrib_ref->{svn}->get_info($path)->[0]};
153    my @keys = map {"conflict:$_-file"} qw{prev-wc prev-base cur-base};
154    # Subversion 1.6: conflict filenames are relative paths.
155    # Subversion 1.8: conflict filenames are absolute paths.
156    my ($mine, $older, $yours) = map {
157        rel2abs($_, rel2abs(dirname($path)))
158    } @info{@keys};
159
160    # If $path is newer (by more than a second), it may contain saved changes.
161    if (    -f $path && (stat($path))[9] > (stat($mine))[9] + 1
162        &&  !$PROMPT->question('OVERWRITE', $path)
163    ) {
164        return;
165    }
166
167    # Launch graphic merge tool
168    if (_cm_graphic_merge($attrib_ref, $path, $mine, $older, $yours)) {
169        return; # rc==1, some conflicts not resolved
170    }
171
172    # Prompt user to run "svn resolve --accept working" on the file
173    if ($PROMPT->question('RESOLVE', $path)) {
174        $attrib_ref->{svn}->call(qw{resolve --accept working}, $path);
175    }
176}
177
178# Resolve a tree conflict.
179sub _cm_resolve_tree_conflict {
180    my ($attrib_ref, $option_ref, $path, @status_lines) = @_;
181    my $PROMPT = $attrib_ref->{prompt};
182    my $UTIL = $attrib_ref->{util};
183
184    # Skip directories - too complex for now.
185    if (-d $path) {
186        $UTIL->event(FCM::Context::Event->CM_CONFLICT_TREE_SKIP, $path);
187        return;
188    }
189
190    # Get basic information about the tree conflict, and the filename.
191    my %info = %{$attrib_ref->{svn}->get_info($path)->[0]};
192
193    # Skip non-existent or unhandled tree conflicts.
194    if (!exists($info{'tree-conflict:operation'})) {
195        return
196    }
197    if ($info{'tree-conflict:operation'} ne 'merge') {
198        $UTIL->event(FCM::Context::Event->CM_CONFLICT_TREE_SKIP, $path);
199        return;
200    }
201   
202    my $tree_reason = $info{'tree-conflict:reason'};
203    if (grep {$tree_reason eq $_} keys(%TREE_CONFLICT_GET_UNALIAS_FOR)) {
204        $tree_reason = $TREE_CONFLICT_GET_UNALIAS_FOR{$tree_reason};
205    }
206    my $tree_key = FCM::System::CM::TreeConflictKey->new(
207        {   'local'    => $tree_reason,
208            'incoming' => $info{'tree-conflict:action'},
209            'type'     => $info{'tree-conflict:operation'},
210        },
211    );
212    my $tree_filename = $info{'path'};
213
214    my %wc_info = %{$attrib_ref->{svn}->get_info()->[0]};
215
216    my $repos_root = $wc_info{'repository:root'};
217    my $wc_branch = substr($wc_info{'url'}, length($repos_root) + 1);
218
219    my $tree_full_filename = '/' . $wc_branch . '/' . $tree_filename;
220
221    # Check for external renaming, by examining files added with history
222    my $ext_renamed_file = '';
223    my $ext_branch = '';
224    COPIED_FILE:
225    for my $copied_file (map {($_ =~ $RE{ST_COPIED})} @status_lines) {
226        my %copy_info = %{$attrib_ref->{svn}->get_info($copied_file)->[0]};
227        my $url = (
228              $copy_info{'wc-info:copy-from-url'}
229            . '@' . $copy_info{'wc-info:copy-from-rev'}
230        );
231        my $copy_log_ref = $attrib_ref->{svn}->get_log($url);
232        if (!$ext_branch) {
233            my $copy_full_path = substr(
234                $copy_info{'wc-info:copy-from-url'},
235                length($repos_root) + 1,
236            );
237            $ext_branch = substr(
238                $copy_full_path,
239                0,
240                -length($copied_file) - 1,
241            );
242        }
243        my $copied_full_filename = '/' . $ext_branch . '/' . $copied_file;
244        my $tree_ext_name
245            = ('/' . $info{'tree-conflict:source-right:path-in-repos'});
246        my $search_name = $tree_ext_name;
247        for my $log_entry_ref (reverse(@{$copy_log_ref})) {
248            for my $path_entry (@{$log_entry_ref->{'paths'}}) {
249                if (    exists $path_entry->{'copyfrom-path'}
250                    &&  $path_entry->{'copyfrom-path'} eq $search_name
251                ) {
252                    $search_name = $path_entry->{'path'};
253                    if ($search_name eq $copied_full_filename) {
254                        $ext_renamed_file = $copied_file;
255                        last COPIED_FILE;
256                    }
257                }
258            }
259        }
260    }
261
262    # Check for local renaming of the tree conflict file
263    my $local_renamed_file = '';
264    if ($tree_reason eq 'delete') {
265        $local_renamed_file = _cm_tree_conflict_get_local_rename(
266            $attrib_ref,
267            $wc_branch,
268            $tree_full_filename,
269            $ext_renamed_file
270        );
271    }
272
273    # The tree conflict identifier (tree_key) needs to be adjusted for reality
274    if ($local_renamed_file) {
275        $tree_key->set_local('rename');
276    }
277    if ($ext_renamed_file) {
278        $tree_key->set_incoming('rename');
279    }
280
281    # Skip and return if the tree key does not match a key in final cmds.
282    my $cmds_getter
283        = $TREE_CONFLICT_GET_FINAL_ACTIONS_FUNC_FOR{$tree_key->as_string()};
284    if (!$cmds_getter) {
285        $UTIL->event(FCM::Context::Event->CM_CONFLICT_TREE_SKIP, $path);
286        return;
287    }
288
289    # Print the tree conflict event message
290    $UTIL->event(FCM::Context::Event->CM_CONFLICT_TREE, $path);
291
292    # These are the relevant files for this tree conflict.
293    my @file_args = grep {$_} ($path, $local_renamed_file, $ext_renamed_file);
294
295    # Prompt which version of events to accept - local or incoming.
296    my $keep_local = $PROMPT->question(
297        'TC_' . $tree_key->as_string(),
298        $tree_key, $local_renamed_file, $ext_renamed_file,
299    );
300
301    # Add any graphic merge commands
302    my @cmds = _cm_tree_conflict_get_graphic_cmds(
303        $attrib_ref, $tree_key->as_string(), $keep_local, \@file_args,
304    );
305    # Now load any miscellaneous actions or commands - for example 'svn delete'
306    if ($tree_key->get_local() eq 'add' && $tree_key->get_incoming() eq 'add') {
307        # We need to generate a new filename and a temporary one in this case.
308        @file_args = ($path);
309        my $tree_dir = dirname($path);
310        my $newfile_handle = File::Temp->new(
311            DIR => $tree_dir,
312            TEMPLATE => basename($path) . '.XXXX',
313            UNLINK => 0,
314        );
315        unlink("$newfile_handle");  # Delete it, or it will block the copy.
316        push(@file_args, "$newfile_handle");
317    }
318    push(@cmds, $cmds_getter->($attrib_ref, $keep_local, \@file_args));
319
320    # Run the actions, including any subroutine references.
321    for my $cmd_ref (@cmds) {
322        $cmd_ref->();
323    }
324    # svn resolve.
325    $attrib_ref->{svn}->call(qw{resolve --accept working}, $path);
326}
327
328# Tree conflicts: check if a file was renamed locally.
329sub _cm_tree_conflict_get_local_rename {
330    my ($attrib_ref, $wc_branch, $tree_full_filename, $ext_renamed_file) = @_;
331    my $UTIL = $attrib_ref->{util};
332
333    # Get the verbose log for the working copy.
334    # Find the revision where the file was deleted, and store any copied
335    # filenames at that revision, and since that revision.
336    my ($d_rev, @rev_copied_filenames, $found_delete);
337    my @since_copied_filenames;
338    my $wc_log_ref = $attrib_ref->{svn}->get_log();
339    ENTRY:
340    for my $log_entry_ref (@{$wc_log_ref}) {
341        $d_rev = $log_entry_ref->{'revision'};
342        @rev_copied_filenames = ();
343        for my $path_entry (@{$log_entry_ref->{'paths'}}) {
344            if ($path_entry->{'copyfrom-path'}) {
345                push(@rev_copied_filenames, $path_entry->{'path'});
346                push(@since_copied_filenames, $path_entry->{'path'});
347            }
348            if (    $path_entry->{'path'} eq $tree_full_filename
349                &&  $path_entry->{'action'} eq 'D'
350            ) {
351                $found_delete = 1;
352            }
353        }
354        if ($found_delete) {
355            last ENTRY;
356        }
357    }
358
359    # We need to detect a copy and a deletion of the file to continue.
360    if (!$found_delete || !@rev_copied_filenames) {
361        return;
362    }
363
364    # Get rid of the branch name in our copied files
365    for my $copied_file (@rev_copied_filenames) {
366        $copied_file =~ s{^/$wc_branch/}{}msx;
367    }
368    for my $copied_file (@since_copied_filenames) {
369        $copied_file =~ s{^/$wc_branch/}{}msx;
370    }
371
372    # Get the pre-existing working copy files.
373    my @wc_files
374        = grep {$_ && $_ =~ qr{[^/]$}msx}
375            $attrib_ref->{svn}->stdout(qw{svn ls -R});
376
377    # Examine the files added with history at the revision log.
378    # A single rename will be detected here.
379    my @result;
380    COPIED_AT_REV_FILE:
381    for my $file (@rev_copied_filenames) {
382        if (!grep {$_ eq $file} @wc_files) {
383            next COPIED_AT_REV_FILE;
384        }
385        my @log_entry_refs
386            = @{$attrib_ref->{svn}->get_log($file . '@' . $d_rev)};
387        my $search_name = $tree_full_filename;
388        my $full_potential_rename = '/' . $wc_branch . '/' . $file;
389        for my $log_entry_ref (@log_entry_refs) {
390            for my $path_entry (@{$log_entry_ref->{'paths'}}) {
391                if (    exists($path_entry->{'copyfrom-path'})
392                    &&  $path_entry->{'copyfrom-path'} eq $tree_full_filename
393                    &&  $path_entry->{'action'} eq 'A'
394                    &&  $path_entry->{'path'} eq $full_potential_rename
395                ) {
396                    return $file;
397                }
398            }
399        }
400    }
401
402    # If no rename was detected, there may have been more than one.
403    # Get the logs for all current filenames that match copied filenames
404    # since the deletion, according to the working copy log.
405
406    # Warn if the number of files to be examined > the threshold.
407    if (    @wc_files > $TREE_CONFLICT_WARN_FILES_THRESHOLD
408        &&  @since_copied_filenames > $TREE_CONFLICT_WARN_FILES_THRESHOLD
409    ) {
410        my $tree_path = substr($tree_full_filename, length($wc_branch) + 2);
411        $UTIL->event(
412            FCM::Context::Event->CM_CONFLICT_TREE_TIME_WARN,
413            $tree_path,
414        );
415    }
416    WC_FILE:
417    for my $file (@wc_files) {
418        if (!grep {$_ eq $file} @since_copied_filenames) {
419            next WC_FILE;
420        }
421        my @log_entry_refs = @{$attrib_ref->{svn}->get_log($file)};
422        my $search_name = $tree_full_filename;
423        my $full_potential_rename = '/' . $wc_branch . '/' . $file;
424        for my $log_entry_ref (reverse(@log_entry_refs)) {
425            my $revision = $log_entry_ref->{'revision'};
426            for my $path_entry (@{$log_entry_ref->{'paths'}}) {
427                if (    exists($path_entry->{'copyfrom-path'})
428                    &&  $path_entry->{'copyfrom-path'} eq $search_name
429                    &&  $path_entry->{'action'} eq 'A'
430                ) {
431                    $search_name = $path_entry->{'path'};
432                    if (   $search_name eq $full_potential_rename
433                        && $revision >= $d_rev
434                    ) {
435                        return $file;
436                    }
437                }
438            }
439        }
440    }
441    return;
442}
443
444# Return the tree conflict command related to fcm_graphic_merge.
445sub _cm_tree_conflict_get_graphic_cmds {
446    my ($attrib_ref, $key, $keep_local, $files_ref) = @_;
447    my ($cfile, @rename_args) = @{$files_ref};
448    if (!@rename_args) {
449        return;
450    }
451    # Get the source argument subroutine reference, if it exists.
452    my $get_srcs_func_ref = $TREE_CONFLICT_GET_GRAPHIC_SOURCES_FUNC_FOR{$key};
453    if (!$get_srcs_func_ref) {
454        return;
455    }
456    # Get the sources for the graphic merge files.
457    my ($older_src, $merge_src, $working_src, $base)
458        = $get_srcs_func_ref->($cfile, $keep_local, \@rename_args);
459
460    # Set up the filenames.
461    my ($older_url, $older_peg)
462        = _cm_tree_conflict_source($attrib_ref, 'left', $older_src);
463    my $mine = $base . '.working';
464    my $older = $base . '.merge-left.r' . $older_peg;
465    my ($merge_url, $merge_peg)
466        = _cm_tree_conflict_source($attrib_ref, 'right', $merge_src);
467    my $yours = $base . '.merge-right.r' . $merge_peg;
468    # Set up the conflict files as in a text conflict.
469    sub {
470        $attrib_ref->{svn}->call(qw{export -q}, $older_url, $older);
471        $attrib_ref->{svn}->call(qw{export -q}, $merge_url, $yours);
472        copy($working_src, $mine);
473        _cm_graphic_merge($attrib_ref, $base, $mine, $older, $yours);
474        unlink($mine, $older, $yours);
475    };
476}
477
478# Return the source-left or source-right url from svn info.
479sub _cm_tree_conflict_source {
480    my ($attrib_ref, $direction, $info_filename) = @_;
481    my %info = %{$attrib_ref->{svn}->get_info($info_filename)->[0]};
482    my ($source_url, $source_peg);
483    if ($info{"tree-conflict:source-$direction:repos-url"}) {
484        $source_url
485            = $info{"tree-conflict:source-$direction:repos-url"}
486            . '/'
487            . $info{"tree-conflict:source-$direction:path-in-repos"};
488        $source_peg = $info{"tree-conflict:source-$direction:revision"};
489    }
490    elsif ($direction eq 'right') {
491        $source_url = $info{'wc-info:copy-from-url'};
492        $source_peg = $info{'wc-info:copy-from-rev'};
493    }
494    ($source_url . '@' . $source_peg, $source_peg);
495}
496
497# Select the files needed for the xxdiff (local edit, incoming rename)
498sub _cm_tree_conflict_get_graphic_sources_for_leir {
499    my ($cfile, $keep_local, $renames) = @_;
500    my $ext_rename = shift(@{$renames});
501    (   $cfile,
502        $ext_rename,
503        $cfile,
504        ($keep_local ? $cfile : $ext_rename),
505    );
506}
507
508# Select the files needed for the xxdiff (local rename, incoming edit)
509sub _cm_tree_conflict_get_graphic_sources_for_lrie {
510    my ($cfile, $keep_local, $renames) = @_;
511    my $local_rename = shift(@{$renames});
512    (   $cfile,
513        $cfile,
514        $local_rename,
515        $local_rename,
516    );
517}
518
519# Select the files needed for the xxdiff (local rename, incoming rename)
520sub _cm_tree_conflict_get_graphic_sources_for_lrir {
521    my ($cfile, $keep_local, $renames) = @_;
522    my ($local_rename, $ext_rename) = @{$renames};
523    (   $cfile,
524        $ext_rename,
525        $local_rename,
526        ($keep_local ? $local_rename : $ext_rename),
527    );
528}
529
530# Return the actions needed to resolve 'local add, incoming add'
531sub _cm_tree_conflict_get_actions_for_laia {
532    my ($attrib_ref, $keep_local, $files_ref) = @_;
533    my ($cfile, $new_name) = @{$files_ref};
534    my ($url, $url_peg) = _cm_tree_conflict_source($attrib_ref, 'right', $cfile);
535    my ($basename) = basename($cfile);
536    my $cdir = dirname($cfile);
537    sub {
538        if (!$keep_local) {
539            my $content = $attrib_ref->{svn}->stdout(qw{svn cat}, $url);
540            $attrib_ref->{util}->file_save($cfile, $content);
541        }
542    };
543}
544
545
546# Return the actions needed to resolve 'local missing, incoming edit'
547sub _cm_tree_conflict_get_actions_for_ldie {
548    my ($attrib_ref, $keep_local, $files_ref) = @_;
549    my ($cfile) = @{$files_ref};
550    my ($url, $url_peg) = _cm_tree_conflict_source($attrib_ref, 'right', $cfile);
551    my $cdir = dirname($cfile);
552    sub {
553        if (!$keep_local) {
554            $attrib_ref->{svn}->call('copy', $url, "$cdir/");
555        }
556    };
557}
558
559# Return the actions needed to resolve 'local delete, incoming rename'
560sub _cm_tree_conflict_get_actions_for_ldir {
561    my ($attrib_ref, $keep_local, $files_ref) = @_;
562    my ($cfile, $ext_rename) = @{$files_ref};
563    sub {
564        if ($keep_local) {
565            $attrib_ref->{svn}->call('revert', $ext_rename);
566            unlink($ext_rename);
567        }
568    };
569}
570
571# Return the actions needed to resolve 'local edit, incoming delete'
572sub _cm_tree_conflict_get_actions_for_leid {
573    my ($attrib_ref, $keep_local, $files_ref) = @_;
574    my ($cfile) = @{$files_ref};
575    sub {
576        if (!$keep_local) {
577            $attrib_ref->{svn}->call('delete', $cfile);
578        }
579    };
580}
581
582# Return the actions needed to resolve 'local edit, incoming replace'
583sub _cm_tree_conflict_get_actions_for_leip {
584    my ($attrib_ref, $keep_local, $files_ref) = @_;
585    my ($cfile) = @{$files_ref};
586    my ($url, $url_peg) = _cm_tree_conflict_source($attrib_ref, 'right', $cfile);
587    my $cdir = dirname($cfile);
588    sub {
589        if (!$keep_local) {
590            $attrib_ref->{svn}->call('delete', $cfile);
591            $attrib_ref->{svn}->call('copy', $url, "$cdir/");
592        }
593    };
594}
595
596# Return the actions needed to resolve 'local edit, incoming rename'
597sub _cm_tree_conflict_get_actions_for_leir {
598    my ($attrib_ref, $keep_local, $files_ref) = @_;
599    my ($cfile, $ext_rename) = @{$files_ref};
600    sub {
601        if ($keep_local) {
602            $attrib_ref->{svn}->call('revert', $ext_rename);
603            unlink($ext_rename);
604        }
605        else {
606            $attrib_ref->{svn}->call('delete', $cfile);
607        }
608    };
609}
610
611# Return the actions needed to resolve 'local rename, incoming delete'
612sub _cm_tree_conflict_get_actions_for_lrid {
613    my ($attrib_ref, $keep_local, $files_ref) = @_;
614    my ($cfile, $lcl_rename) = @{$files_ref};
615    sub {
616        if (!$keep_local) {
617            $attrib_ref->{svn}->call('delete', $lcl_rename);
618        }
619    };
620}
621
622# Return the actions needed to resolve 'local rename, incoming edit'
623sub _cm_tree_conflict_get_actions_for_lrie {
624    my ($attrib_ref, $keep_local, $files_ref) = @_;
625    my ($cfile, $lcl_rename) = @{$files_ref};
626    sub {
627        if (!$keep_local) {
628            $attrib_ref->{svn}->call('rename', $lcl_rename, $cfile)
629        }
630    };
631}
632
633# Return the actions needed to resolve 'local rename, incoming rename'
634sub _cm_tree_conflict_get_actions_for_lrir {
635    my ($attrib_ref, $keep_local, $files_ref) = @_;
636    my ($cfile, $lcl_rename, $ext_rename) = @{$files_ref};
637    sub {
638        if ($keep_local) {
639            $attrib_ref->{svn}->call('revert', $ext_rename);
640            unlink($ext_rename);
641        }
642        else {
643            $attrib_ref->{svn}->call('delete', $lcl_rename);
644        }
645    };
646}
647
648# -----------------------------------------------------------------------------
649# Stores the identifier of the type of tree conflict.
650package FCM::System::CM::TreeConflictKey;
651use base qw{FCM::Class::HASH};
652
653# Creates the class.
654# 'local' is the local change (e.g. edit or delete),
655# 'incoming' is the external change (e.g. add or rename),
656# 'type' is one of merge, switch, or update.
657__PACKAGE__->class({'local' => '$', 'incoming' => '$', 'type' => '$'});
658
659# Returns a label string of the form LXIY e.g. LEID for local edit,
660# incoming delete.
661sub as_string {
662    my ($self) = shift();
663    my $local = $self->get_local() eq 'replace'
664        ? 'P' : uc(substr($self->get_local(), 0, 1));
665    my $incoming = $self->get_incoming() eq 'replace'
666        ? 'P' : uc(substr($self->get_incoming(), 0, 1));
667    sprintf('L%sI%s', $local, $incoming);
668}
669
6701;
671__END__
672
673=head1 NAME
674
675FCM::System::CM::ResolveConflicts
676
677=head1 DESCRIPTION
678
679Part of L<FCM::System::CM|FCM::System::CM>.
680
681=head1 COPYRIGHT
682
683(C) Crown copyright Met Office. All rights reserved.
684
685=cut
Note: See TracBrowser for help on using the repository browser.