CmCommitMessage.pm 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. # ------------------------------------------------------------------------------
  2. # NAME
  3. # Fcm::CmCommitMessage
  4. #
  5. # DESCRIPTION
  6. # This class contains methods to read, write and edit the commit message file
  7. # in a working copy.
  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. package Fcm::CmCommitMessage;
  15. @ISA = qw(Fcm::Base);
  16. # Standard pragma
  17. use warnings;
  18. use strict;
  19. # Standard modules
  20. use Carp;
  21. use Cwd;
  22. use File::Spec;
  23. use File::Temp qw/tempfile/;
  24. # FCM component modules
  25. use Fcm::Base;
  26. use Fcm::Util qw/e_report run_command/;
  27. # List of property methods for this class
  28. my @scalar_properties = (
  29. 'auto_mesg', # the automatically inserted part of a commit message
  30. 'base', # the base name of the commit message file
  31. 'dir', # the directory container of the commit message file
  32. 'ignore_mesg', # the ignored part of a commit message
  33. 'user_mesg', # the user defined part of a commit message
  34. );
  35. # Commit log delimiter messages
  36. my $log_delimiter = '--Add your commit message ABOVE - ' .
  37. 'do not alter this line or those below--';
  38. my $auto_delimiter = '--FCM message (will be inserted automatically)--';
  39. my $auto_delimiter_old = '--This line will be ignored and those below ' .
  40. 'will be inserted automatically--';
  41. my $status_delimiter = '--Change summary ' .
  42. '(not part of commit message)--';
  43. my $status_delimiter_old = '--This line, and those below, will be ignored--';
  44. # ------------------------------------------------------------------------------
  45. # SYNOPSIS
  46. # $obj = Fcm::CmCommitMessage->new ();
  47. #
  48. # DESCRIPTION
  49. # This method constructs a new instance of the Fcm::CmCommitMessage class.
  50. # ------------------------------------------------------------------------------
  51. sub new {
  52. my $this = shift;
  53. my %args = @_;
  54. my $class = ref $this || $this;
  55. my $self = Fcm::Base->new (%args);
  56. $self->{$_} = undef for (@scalar_properties);
  57. bless $self, $class;
  58. return $self;
  59. }
  60. # ------------------------------------------------------------------------------
  61. # SYNOPSIS
  62. # $value = $obj->X;
  63. # $obj->X ($value);
  64. #
  65. # DESCRIPTION
  66. # Details of these properties are explained in @scalar_properties.
  67. # ------------------------------------------------------------------------------
  68. for my $name (@scalar_properties) {
  69. no strict 'refs';
  70. *$name = sub {
  71. my $self = shift;
  72. # Argument specified, set property to specified argument
  73. if (@_) {
  74. $self->{$name} = $_[0];
  75. }
  76. # Default value for property
  77. if (not defined $self->{$name}) {
  78. if ($name eq 'base') {
  79. # Reference to an array
  80. $self->{$name} = '#commit_message#';
  81. } elsif ($name eq 'dir') {
  82. # Current working directory
  83. $self->{$name} = &cwd ();
  84. } elsif ($name =~ /_mesg$/) {
  85. # Reference to an array
  86. $self->{$name} = [];
  87. }
  88. }
  89. return $self->{$name};
  90. }
  91. }
  92. # ------------------------------------------------------------------------------
  93. # SYNOPSIS
  94. # $file = $obj->file;
  95. # $obj->file ($file);
  96. #
  97. # DESCRIPTION
  98. # This method returns the full name of the commit message file. If an
  99. # argument is specified, the file is reset using the value of the argument.
  100. # ------------------------------------------------------------------------------
  101. sub file {
  102. my ($self, $file) = @_;
  103. if ($file) {
  104. $self->dir (dirname ($file));
  105. $self->base (basename ($file));
  106. }
  107. return File::Spec->catfile ($self->dir, $self->base);
  108. }
  109. # ------------------------------------------------------------------------------
  110. # SYNOPSIS
  111. # ($user, $auto) = $obj->read_file ();
  112. #
  113. # DESCRIPTION
  114. # This function reads from the commit log message file. It resets the user
  115. # and the automatic messages after reading the file. It returns the message
  116. # back in two array references.
  117. # ------------------------------------------------------------------------------
  118. sub read_file {
  119. my $self = shift;
  120. my @user = ();
  121. my @auto = ();
  122. my $file = $self->file;
  123. if (-r $file) {
  124. open FILE, '<', $file or croak 'Cannot open ', $file, '(', $!, '), abort';
  125. my $in_auto = 0;
  126. while (<FILE>) {
  127. next if (index ($_, $log_delimiter) == 0);
  128. if (index ($_, $status_delimiter) == 0 ||
  129. index ($_, $status_delimiter_old) == 0) {
  130. # Ignore after the ignore delimiter
  131. last;
  132. }
  133. if (index ($_, $auto_delimiter) == 0 ||
  134. index ($_, $auto_delimiter_old) == 0) {
  135. # Beginning of the automatically inserted message
  136. $in_auto = 1;
  137. next;
  138. }
  139. if ($in_auto) {
  140. push @auto, $_;
  141. } else {
  142. push @user, $_;
  143. }
  144. }
  145. close FILE;
  146. $self->user_mesg (\@user);
  147. $self->auto_mesg (\@auto);
  148. }
  149. return (\@user, \@auto);
  150. }
  151. # ------------------------------------------------------------------------------
  152. # SYNOPSIS
  153. # $obj->write_file ();
  154. #
  155. # DESCRIPTION
  156. # This function writes to the commit log message file based on the content of
  157. # the user defined message, and the automatically inserted message.
  158. # ------------------------------------------------------------------------------
  159. sub write_file {
  160. my $self = shift;
  161. my %args = @_;
  162. my @user = @{ $self->user_mesg };
  163. my @auto = @{ $self->auto_mesg };
  164. my $file = $self->file;
  165. open FILE, '>', $file or die 'Cannot open ', $file, '(', $!, '), abort';
  166. print FILE @user;
  167. print FILE $log_delimiter, "\n", $auto_delimiter, "\n", @auto if @auto;
  168. close FILE or croak 'Cannot close ', $file, '(', $!, '), abort';
  169. return;
  170. }
  171. # ------------------------------------------------------------------------------
  172. # SYNOPSIS
  173. # $file = $obj->edit_file ([TEMP => 1,] [BATCH => 1,]);
  174. #
  175. # DESCRIPTION
  176. # This function normally triggers an editor for editing the commit message.
  177. # If TEMP is set, it edits a temporary file. Otherwise, it edits the current
  178. # commit message file. It resets the user defined message on success. Returns
  179. # the name of the commit log file. Do not start the editor if BATCH is set.
  180. # ------------------------------------------------------------------------------
  181. sub edit_file {
  182. my $self = shift;
  183. my %args = @_;
  184. my $temp = exists $args{TEMP} ? $args{TEMP} : 0;
  185. my $batch = exists $args{BATCH} ? $args{BATCH} : 0;
  186. my @user = @{ $self->user_mesg };
  187. my @auto = @{ $self->auto_mesg };
  188. my @ignore = @{ $self->ignore_mesg };
  189. my $file = $self->file;
  190. if ($temp) {
  191. my $fh;
  192. ($fh, $file) = tempfile (SUFFIX => ".fcm", UNLINK => 1);
  193. close $fh;
  194. }
  195. # Add original or code driven message and status information to the file
  196. my $select = select;
  197. open FILE, '>', $file or croak 'Cannot open ', $file, ' (', $!, '), abort';
  198. select FILE;
  199. print @user;
  200. print (@auto || @user ? '' : "\n");
  201. print $log_delimiter, "\n";
  202. print $auto_delimiter, "\n", @auto, "\n" if @auto;
  203. print $status_delimiter, "\n\n";
  204. print @ignore if @ignore;
  205. close FILE or die 'Cannot close ', $file, ' (', $!, '), abort';
  206. select $select;
  207. if (not $batch) {
  208. # Select editor
  209. my $editor = 'nedit';
  210. if ($ENV{'SVN_EDITOR'}) {
  211. $editor = $ENV{'SVN_EDITOR'};
  212. } elsif ($ENV{'VISUAL'}) {
  213. $editor = $ENV{'VISUAL'};
  214. } elsif ($ENV{'EDITOR'}) {
  215. $editor = $ENV{'EDITOR'};
  216. }
  217. # Execute command to start the editor
  218. print 'Starting ', $editor, ' to edit commit message ...', "\n";
  219. &run_command ([split (/\s+/, $editor), $file]);
  220. }
  221. # Read the edited file, and extract user log message from it
  222. open FILE, '<', $file or croak 'Cannot open ', $file, ' (', $!, '), abort';
  223. my (@log);
  224. my $delimiter_found = 0;
  225. while (<FILE>) {
  226. if (index ($_, $log_delimiter) == 0) {
  227. $delimiter_found = 1;
  228. last;
  229. }
  230. push @log, $_;
  231. }
  232. close FILE;
  233. # Ensure log delimiter line was not altered
  234. e_report 'Error: the line "', $log_delimiter, '" has been altered, abort.'
  235. if not $delimiter_found;
  236. # Check for empty commit log
  237. e_report 'Error: log message unchanged or not specified, abort.'
  238. if join (' ', (@log, @auto)) =~ /^\s*$/;
  239. # Echo the commit message to standard output
  240. my $separator = '-' x 80 . "\n";
  241. print 'Change summary:', "\n";
  242. print $separator, @ignore, $separator;
  243. print 'Commit message is as follows:', "\n";
  244. print $separator, @log, @auto, $separator;
  245. open FILE, '>', $file or croak 'Cannot open ', $file, ' (', $!, '), abort';
  246. print FILE @log, @auto;
  247. close FILE or croak 'Cannot close ', $file, ' (', $!, '), abort';
  248. # Reset the array for the user specified log message
  249. $self->user_mesg (\@log);
  250. return $file;
  251. }
  252. # ------------------------------------------------------------------------------
  253. 1;
  254. __END__