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.
CommitMessage.pm in vendors/lib/FCM/System/CM – NEMO

source: vendors/lib/FCM/System/CM/CommitMessage.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: 9.5 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# Utility to manipulate FCM commit messages.
23package FCM::System::CM::CommitMessage;
24use base qw{FCM::Class::CODE};
25
26use Cwd qw{cwd};
27use FCM::Context::Event;
28use FCM::System::Exception;
29use File::Spec::Functions qw{catfile};
30use File::Temp;
31use Text::ParseWords qw{shellwords};
32
33my $CTX = 'FCM::System::CM::CommitMessage::State';
34my $E = 'FCM::System::Exception';
35
36our $COMMIT_MESSAGE_BASE = '#commit_message#';
37our $DELIMITER_USER
38    = '--Add your commit message ABOVE - do not alter this line or those below--'
39    . "\n";
40our $DELIMITER_AUTO
41    = '--FCM message (will be inserted automatically)--'
42    . "\n";
43our $DELIMITER_INFO
44    = '--Change summary (not part of commit message)--'
45    . "\n";
46our $EDITOR = 'vi';
47our $GEDITOR = 'gvim -f';
48our $SUBVERSION_CONFIG_FILE = catfile((getpwuid($<))[7], qw{.subversion/config});
49
50__PACKAGE__->class({gui => '$', util => '&'},
51    {action_of => {
52        'ctx'       => sub {$CTX->new()},
53        'edit'      => \&_edit,
54        'load'      => \&_load,
55        'notify'    => \&_notify,
56        'path'      => \&_path,
57        'path_base' => sub {$COMMIT_MESSAGE_BASE},
58        'save'      => \&_save,
59        'temp'      => \&_temp,
60    }},
61);
62
63# Invokes an editor to edit the commit message context.
64sub _edit {
65    my ($attrib_ref, $commit_message_ctx) = @_;
66    my $UTIL = $attrib_ref->{'util'};
67    my $temp = File::Temp->new();
68    if ($commit_message_ctx->get_user_part()) {
69        print($temp $commit_message_ctx->get_user_part());
70    }
71    else {
72        print($temp "\n");
73    }
74    print($temp $DELIMITER_USER);
75    if ($commit_message_ctx->get_auto_part()) {
76        print($temp $DELIMITER_AUTO . $commit_message_ctx->get_auto_part());
77    }
78    print($temp $DELIMITER_INFO . $commit_message_ctx->get_info_part());
79    close($temp) || die("$temp: $!\n");
80    my $config_value;
81    my $editor_command
82        = $ENV{'SVN_EDITOR'} ? $ENV{'SVN_EDITOR'}
83        : ($config_value = _svn_config_get($attrib_ref, 'helpers', 'editor-cmd'))
84                             ? $config_value
85        : $ENV{'VISUAL'}     ? $ENV{'VISUAL'}
86        : $ENV{'EDITOR'}     ? $ENV{'EDITOR'}
87        : $attrib_ref->{gui} ? $GEDITOR
88        :                      $EDITOR
89        ;
90    $UTIL->event(FCM::Context::Event->CM_LOG_EDIT, $editor_command);
91    my @command = (shellwords($editor_command), $temp->filename());
92    !system(@command)
93        || return $E->throw($E->SHELL, {command_list => \@command, rc => $?});
94    # Note: cannot use FCM::Util->shell method for terminal based editor.
95    #my %value_of = %{$attrib_ref->{'util'}->shell_simple(\@command)};
96    #if ($value_of{'rc'}) {
97    #    return $E->throw($E->SHELL, {command_list => \@command, %value_of});
98    #}
99    my $user_part = _parse(
100        $attrib_ref,
101        scalar($UTIL->file_load($temp->filename())),
102        $DELIMITER_USER,
103    );
104    $commit_message_ctx->set_user_part($user_part);
105    if (($user_part . $commit_message_ctx->get_auto_part()) =~ qr{\A\s*\z}msx) {
106        return $E->throw($E->CM_LOG_EDIT_NULL);
107    }
108}
109
110# Reads a commit message file from $path or the standard location. Returns a
111# commit message context object.
112sub _load {
113    my ($attrib_ref, $path) = @_;
114    $path ||= _path($attrib_ref);
115    my ($user_part, $auto_part) = eval {
116        _parse($attrib_ref, scalar($attrib_ref->{'util'}->file_load($path)));
117    };
118    if (my $e = $@) {
119        $user_part = q{};
120        $auto_part = q{};
121        $@ = undef; # TODO: should raise a high verbosity event?
122    }
123    $CTX->new({'user_part' => $user_part, 'auto_part' => $auto_part});
124}
125
126# Raises an CM_COMMIT_MESSAGE event for the commit message.
127sub _notify {
128    my ($attrib_ref, $commit_message_ctx) = @_;
129    $attrib_ref->{util}->event(
130        FCM::Context::Event->CM_COMMIT_MESSAGE, $commit_message_ctx,
131    );
132}
133
134# Parses a commit message into the user and auto parts. Returns the user part in
135# scalar context. Returns (user_part, auto_part) in list context.
136sub _parse {
137    my ($attrib_ref, $message, $no_delimiter_user) = @_;
138    my @parts = (q{}, q{});
139    my $state = 0;
140    LINE:
141    for my $line (split("\n", $message)) {
142        if ($state && !wantarray()) {
143            last LINE;
144        }
145        $line .= "\n";
146        if ($line eq $DELIMITER_INFO) {
147            last LINE;
148        }
149        elsif ($line eq $DELIMITER_AUTO) {
150            $state = 1;
151            next LINE;
152        }
153        elsif ($line eq $DELIMITER_USER) {
154            $no_delimiter_user = undef;
155            $state = -1;
156            next LINE;
157        }
158        if ($state >= 0) {
159            $parts[$state] .= $line;
160        }
161    }
162    if ($no_delimiter_user) {
163        return $E->throw($E->CM_LOG_EDIT_DELIMITER, $DELIMITER_USER);
164    }
165    for my $part (@parts) {
166        $part =~ s{\A\s*(.*?)\s*\z}{$1}msx;
167        if ($part) {
168            $part .= "\n";
169        }
170    }
171    wantarray() ? @parts : $parts[0];
172}
173
174# Returns the path to the commit message file in the current working directory
175# or the commit message file in $dir if $dir is set.
176sub _path {
177    my ($attrib_ref, $dir) = @_;
178    catfile(($dir ? $dir : cwd()), $COMMIT_MESSAGE_BASE);
179}
180
181# Saves the commit message to $path or the standard location for later
182# retrieval.
183sub _save {
184    my ($attrib_ref, $commit_message_ctx, $path) = @_;
185    $path ||= _path($attrib_ref);
186    my $string = $commit_message_ctx->get_user_part();
187    if ($commit_message_ctx->get_auto_part()) {
188        $string .= $DELIMITER_AUTO . $commit_message_ctx->get_auto_part();
189    }
190    $attrib_ref->{'util'}->file_save($path, $string);
191}
192
193# Returns a File::Temp object containing a commit message ready for the VCS.
194sub _temp {
195    my ($attrib_ref, $commit_message_ctx) = @_;
196    my $temp = File::Temp->new();
197    print($temp $commit_message_ctx->get_user_part());
198    print($temp $commit_message_ctx->get_auto_part());
199    close($temp) || die("$temp: $!\n");
200    $temp;
201}
202
203# Loads a setting from $HOME/.subversion/config, and returns its value.
204sub _svn_config_get {
205    my ($attrib_ref, $section, $key) = @_;
206    # Note: can use Config::IniFiles, but best to avoid another dependency.
207    # Note: not very efficient logic here, but should not yet matter.
208    my $handle = $attrib_ref->{'util'}->file_load_handle($SUBVERSION_CONFIG_FILE);
209    my $is_in_section;
210    my $value;
211    LINE:
212    while (my $line = readline($handle)) {
213        chomp($line);
214        if ($line =~ qr{\A\s*(?:[#;]|\z)}msx) {
215            next LINE;
216        }
217        if ($line =~ qr{\A\s*\[\s*$section\s*\]\s*\z}msx) {
218            $is_in_section = 1;
219        }
220        elsif ($line =~ qr{\A\s*\[}msx) {
221            $is_in_section = 0;
222        }
223        elsif ($is_in_section) {
224            my ($rhs) = $line =~ qr{\A\s*$key\s*=\s*(.*)\z}msx;
225            if (defined($rhs)) {
226                $value = $rhs;
227            }
228        }
229    }
230    close($handle);
231    $value;
232}
233
234#-------------------------------------------------------------------------------
235package FCM::System::CM::CommitMessage::State;
236use base qw{FCM::Class::HASH};
237
238__PACKAGE__->class({
239    (map {($_ . '_part' => {isa => '$', default => q{}})} qw{auto info user}),
240});
241
242#-------------------------------------------------------------------------------
2431;
244__END__
245
246=head1 NAME
247
248FCM::System::CM::CommitMessage
249
250=head1 SYNOPSIS
251
252    use FCM::System::CM::CommitMessage;
253    my $commit_message_util = FCM::System::CM::CommitMessage->new(\%attrib);
254    my $commit_message_ctx = $commit_message_util->ctx();
255    $commit_message_util->edit($ctx);
256
257=head1 DESCRIPTION
258
259The commit message dumper, editor, loader, parser, etc for the FCM code
260management sub-system.
261
262=head1 METHODS
263
264=over 4
265
266=item $class->new(\%attrib)
267
268Return a new instance. This class should normally be initialised by
269L<FCM::System::CM|FCM::System::CM>.
270
271=item $commit_message_util->ctx()
272
273Return a new and empty commit message context.
274
275=item $commit_message_util->edit($commit_message_ctx)
276
277Invoke an editor to edit the commit message context.
278
279=item $commit_message_util->load($path)
280
281Load the content of a commit message file in $path, and return the result in a
282new commit message context.
283
284=item $commit_message_util->notify($commit_message_ctx)
285
286Raise a CM_COMMIT_MESSAGE event with the $commit_message_ctx.
287
288=item $commit_message_util->path($dir)
289
290Return the path to the commit message file in $dir or the current working
291directory if $dir is not specified.
292
293=item $commit_message_util->path($dir)
294
295Return the base name of the commit message file.
296
297=item $commit_message_util->save($commit_message_ctx, $path)
298
299Save the commit message to $path (or the standard location if $path is not
300specified).
301
302=item $commit_message_util->temp()
303
304Return a File::Temp object containing a commit message ready for the VCS.
305
306=back
307
308=head1 COPYRIGHT
309
310(C) Crown copyright Met Office. All rights reserved.
311
312=cut
Note: See TracBrowser for help on using the repository browser.