1 //
2 // aegis - project change supervisor
3 // Copyright (C) 1991-2012 Peter Miller
4 // Copyright (C) 2008, 2011 Walter Franzini
5 //
6 // This program 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 (at
9 // your option) any later version.
10 //
11 // This program 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 GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19 
20 #include <common/ac/assert.h>
21 #include <common/ac/stdio.h>
22 #include <common/ac/stdlib.h>
23 #include <common/ac/string.h>
24 #include <common/ac/time.h>
25 #include <common/ac/sys/types.h>
26 #include <common/ac/sys/stat.h>
27 
28 #include <common/progname.h>
29 #include <common/quit.h>
30 #include <common/sizeof.h>
31 #include <common/trace.h>
32 #include <libaegis/ael/change/by_state.h>
33 #include <libaegis/arglex2.h>
34 #include <libaegis/arglex/change.h>
35 #include <libaegis/arglex/project.h>
36 #include <libaegis/attribute.h>
37 #include <libaegis/change/attributes.h>
38 #include <libaegis/change/branch.h>
39 #include <libaegis/change/develop_direct/read_only.h>
40 #include <libaegis/change/file.h>
41 #include <libaegis/change/identifier.h>
42 #include <libaegis/change/signedoffby.h>
43 #include <libaegis/col.h>
44 #include <libaegis/commit.h>
45 #include <libaegis/common.fmtgen.h>
46 #include <libaegis/file.h>
47 #include <libaegis/help.h>
48 #include <libaegis/lock.h>
49 #include <libaegis/option.h>
50 #include <libaegis/os.h>
51 #include <libaegis/project/active.h>
52 #include <libaegis/project/file.h>
53 #include <libaegis/project.h>
54 #include <libaegis/project/history.h>
55 #include <libaegis/rss.h>
56 #include <libaegis/sub.h>
57 #include <libaegis/undo.h>
58 #include <libaegis/user.h>
59 
60 #include <aegis/aede.h>
61 
62 
63 static void
develop_end_usage(void)64 develop_end_usage(void)
65 {
66     const char      *progname;
67 
68     progname = progname_get();
69     fprintf(stderr, "usage: %s -Develop_End [ <option>... ]\n", progname);
70     fprintf(stderr, "       %s -Develop_End -List [ <option>... ]\n", progname);
71     fprintf(stderr, "       %s -Develop_End -Help\n", progname);
72     quit(1);
73 }
74 
75 
76 static void
develop_end_help(void)77 develop_end_help(void)
78 {
79     help("aede", develop_end_usage);
80 }
81 
82 
83 static void
develop_end_list(void)84 develop_end_list(void)
85 {
86     trace(("develop_end_list()\n{\n"));
87     arglex();
88     change_identifier cid;
89     cid.command_line_parse_rest(develop_end_usage);
90     list_changes_in_state_mask(cid, 1 << cstate_state_being_developed);
91     trace(("}\n"));
92 }
93 
94 
95 static void
develop_end_main(void)96 develop_end_main(void)
97 {
98     sub_context_ty  *scp;
99     string_ty       *dd;
100     cstate_ty       *cstate_data;
101     int             j;
102     cstate_history_ty *history_data;
103     int             diff_whine;
104     time_t          youngest;
105     string_ty       *youngest_name;
106     int             config_seen;
107     int             is_a_branch;
108 
109     trace(("develop_end_main()\n{\n"));
110     arglex();
111     string_ty *reason = 0;
112     change_identifier cid;
113     while (arglex_token != arglex_token_eoln)
114     {
115         switch (arglex_token)
116         {
117         default:
118             generic_argument(develop_end_usage);
119             continue;
120 
121         case arglex_token_change:
122         case arglex_token_number:
123         case arglex_token_project:
124         case arglex_token_string:
125             cid.command_line_parse(develop_end_usage);
126             continue;
127 
128         case arglex_token_wait:
129         case arglex_token_wait_not:
130             user_ty::lock_wait_argument(develop_end_usage);
131             break;
132 
133         case arglex_token_signed_off_by:
134         case arglex_token_signed_off_by_not:
135             option_signed_off_by_argument(develop_end_usage);
136             break;
137 
138         case arglex_token_reason:
139             if (reason)
140             duplicate_option(develop_end_usage);
141             switch (arglex())
142             {
143             default:
144                 option_needs_string(arglex_token_reason, develop_end_usage);
145                 // NOTREACHED
146 
147             case arglex_token_string:
148             case arglex_token_number:
149                 reason = str_from_c(arglex_value.alv_string);
150                 break;
151             }
152             break;
153         }
154         arglex();
155     }
156     cid.command_line_check(develop_end_usage);
157     user_ty::pointer up_admin;
158 
159     //
160     // Project administrators are allowed to end the development of
161     // a branch, no matter who created it.
162     //
163     if
164     (
165         cid.get_cp()->is_a_branch()
166     &&
167         nstring(cid.get_cp()->developer_name()) != cid.get_up()->name()
168     &&
169         project_administrator_query(cid.get_pp(), cid.get_up()->name())
170     )
171     {
172         up_admin = cid.get_up();
173         cid.get_up() = user_ty::create(nstring(cid.get_cp()->developer_name()));
174     }
175 
176     //
177     // Take an advisory write lock on the appropriate row of the change
178     // table.  Take an advisory write lock on the appropriate row of the
179     // user table.  Block until can get both simultaneously.
180     //
181     cid.get_pp()->pstate_lock_prepare();
182     change_cstate_lock_prepare(cid.get_cp());
183     cid.get_up()->ustate_lock_prepare();
184     lock_take();
185     cstate_data = cid.get_cp()->cstate_get();
186 
187     //
188     // It is an error if the change is not in the being_developed state.
189     // It is an error if the change is not assigned to the current user.
190     // It is an error if the change has no current diff.
191     // It is an error if the change has no current build.
192     // It is an error if the change has no current test pass.
193     // It is an error if the change has no current baseline test pass.
194     // It is an error if the change has no new test associated with it.
195     //
196     if (cstate_data->state != cstate_state_being_developed)
197         change_fatal(cid.get_cp(), 0, i18n("bad de state"));
198     if (nstring(cid.get_cp()->developer_name()) != cid.get_up()->name())
199         change_fatal(cid.get_cp(), 0, i18n("not developer"));
200     if (!change_file_nth(cid.get_cp(), (size_t)0, view_path_first))
201         change_fatal(cid.get_cp(), 0, i18n("no files"));
202     int number_of_errors = 0;
203     diff_whine = 0;
204     config_seen = 0;
205 
206     //
207     // It is an error if change change attributes mention
208     // architectures not in the project.
209     //
210     change_check_architectures(cid.get_cp());
211 
212     //
213     // It is an error if any files in the change file table have
214     // been modified since the last build.
215     //
216     youngest = 0;
217     youngest_name = 0;
218     is_a_branch = cid.get_cp()->is_a_branch();
219     pconf_ty *pconf_data = change_pconf_get(cid.get_cp(), 1);
220     bool entire_source_hide = true;
221     bool local_source_hide = true;
222     for (j = 0;; ++j)
223     {
224         fstate_src_ty   *c_src_data;
225         fstate_src_ty   *p_src_data;
226         string_ty       *path;
227         string_ty       *path_d;
228         int             same;
229         int             same_d;
230         int             file_required;
231 
232         c_src_data = change_file_nth(cid.get_cp(), j, view_path_first);
233         if (!c_src_data)
234             break;
235         trace(("file_name = \"%s\"\n", c_src_data->file_name->str_text));
236 
237         //
238         // We need to know if all the files in this change set are
239         // marked entire-source-hide so that we can mark the change set
240         // with the aeget:inventory:hide attribute.
241         //
242         bool esh =
243             attributes_list_find_boolean
244             (
245                 c_src_data->attribute,
246                 "entire-source-hide"
247             );
248         bool lsh =
249             attributes_list_find_boolean
250             (
251                 c_src_data->attribute,
252                 "local-source-hide"
253             );
254         if (!esh)
255             entire_source_hide = false;
256         if (!esh && !lsh)
257             local_source_hide = false;
258 
259         switch (pconf_data->unchanged_file_develop_end_policy)
260         {
261         case pconf_unchanged_file_develop_end_policy_ignore:
262             break;
263 
264         case pconf_unchanged_file_develop_end_policy_warning:
265             if (cid.get_cp()->file_unchanged(c_src_data, cid.get_up()))
266             {
267                 sub_context_ty sc;
268                 sc.var_set_string("File_Name", c_src_data->file_name);
269                 change_warning(cid.get_cp(), &sc, i18n("$filename unchanged"));
270             }
271             break;
272 
273         case pconf_unchanged_file_develop_end_policy_error:
274             if (cid.get_cp()->file_unchanged(c_src_data, cid.get_up()))
275             {
276                 sub_context_ty sc;
277                 sc.var_set_string("File_Name", c_src_data->file_name);
278                 change_error(cid.get_cp(), &sc, i18n("$filename unchanged"));
279                 ++number_of_errors;
280             }
281             break;
282         }
283 
284         file_required = 1;
285         bool diff_file_required = change_diff_required(cid.get_cp());
286         trace_bool(diff_file_required);
287         switch (c_src_data->action)
288         {
289         case file_action_create:
290         case file_action_modify:
291         case file_action_insulate:
292             break;
293 
294         case file_action_remove:
295             file_required = 0;
296             if (is_a_branch)
297             {
298                 fstate_src_ty   *src;
299 
300                 src =
301                     cid.get_pp()->file_find
302                     (
303                         c_src_data->file_name,
304                         view_path_extreme
305                     );
306                 if (!src)
307                 {
308                     diff_file_required = false;
309                 }
310             }
311 
312             //
313             // The removed half of a move is not differenced.
314             //
315             if
316             (
317                 c_src_data->move
318             &&
319                 cid.get_cp()->file_find
320                 (
321                     nstring(c_src_data->move),
322                     view_path_first
323                 )
324             )
325                 diff_file_required = false;
326             break;
327 
328         case file_action_transparent:
329             //
330             // Don't check anything for branches (the file is going).
331             // For changes, make sure it's the same as the ancestor.
332             //
333             diff_file_required = false;
334             file_required = 0;
335             if (!is_a_branch)
336             {
337                 assert(cid.get_pp()->parent_get());
338                 if (!cid.get_pp()->is_a_trunk())
339                 {
340                     fstate_src_ty   *pp_src;
341 
342                     pp_src =
343                         cid.get_pp()->parent_get()->file_find
344                         (
345                             c_src_data->file_name,
346                             view_path_extreme
347                         );
348                     if (pp_src)
349                     {
350                         string_ty       *blf;
351                         int             different;
352 
353                         path = cid.get_cp()->file_path(c_src_data->file_name);
354                         blf =
355                             project_file_path
356                             (
357                                 cid.get_pp()->parent_get(),
358                                 c_src_data->file_name
359                             );
360                         assert(blf);
361                         cid.get_up()->become_begin();
362                         different = files_are_different(path, blf);
363                         cid.get_up()->become_end();
364                         str_free(blf);
365                         str_free(path);
366                         if (different)
367                         {
368                             scp = sub_context_new();
369                             sub_var_set_string
370                             (
371                                 scp,
372                                 "File_Name",
373                                 c_src_data->file_name
374                             );
375                             change_error
376                             (
377                                 cid.get_cp(),
378                                 scp,
379                                 i18n("$filename altered")
380                             );
381                             sub_context_delete(scp);
382                             ++number_of_errors;
383                         }
384                     }
385                 }
386             }
387             break;
388         }
389         switch (c_src_data->usage)
390         {
391         case file_usage_build:
392             file_required = 0;
393             diff_file_required = false;
394             break;
395 
396         case file_usage_source:
397         case file_usage_config:
398         case file_usage_test:
399         case file_usage_manual_test:
400             break;
401         }
402 
403         //
404         // the config file in a change
405         // implies additional tests
406         //
407         if (cid.get_cp()->file_is_config(c_src_data->file_name))
408             config_seen++;
409 
410         //
411         // make sure the file exists and has not altered
412         //
413         path = cid.get_cp()->file_path(c_src_data->file_name);
414         if (file_required)
415         {
416             if (!c_src_data->file_fp)
417             {
418                 c_src_data->file_fp =
419                     (fingerprint_ty *)fingerprint_type.alloc();
420             }
421             assert(c_src_data->file_fp->youngest>=0);
422             assert(c_src_data->file_fp->oldest>=0);
423             cid.get_up()->become_begin();
424             same = change_fingerprint_same(c_src_data->file_fp, path, 0);
425             cid.get_up()->become_end();
426             assert(c_src_data->file_fp->youngest>0);
427             assert(c_src_data->file_fp->oldest>0);
428             trace(("same = %d\n", same));
429 
430             if (!c_src_data->file_fp || !c_src_data->file_fp->youngest)
431             {
432                 scp = sub_context_new();
433                 sub_var_set_string(scp, "File_Name", c_src_data->file_name);
434                 change_error(cid.get_cp(), scp, i18n("$filename not found"));
435                 sub_context_delete(scp);
436                 str_free(path);
437                 ++number_of_errors;
438                 continue;
439             }
440 
441             if (c_src_data->file_fp->oldest > youngest)
442             {
443                 youngest = c_src_data->file_fp->oldest;
444                 youngest_name = c_src_data->file_name;
445             }
446         }
447         else
448             same = 1;
449 
450         //
451         // make sure the filename conforms to length limits
452         //
453         // Scenario: user copies "config", alters filename
454         // constraints, creates file, uncopies "config".
455         // Reviewer will not necessarily notice, especially when
456         // expecting aegis to notice for him.
457         //
458         if (!is_a_branch && file_required)
459         {
460             string_ty       *e;
461 
462             e = change_filename_check(cid.get_cp(), c_src_data->file_name);
463             if (e)
464             {
465                 scp = sub_context_new();
466                 sub_var_set_string(scp, "MeSsaGe", e);
467                 str_free(e);
468                 change_error(cid.get_cp(), scp, i18n("$message"));
469                 sub_context_delete(scp);
470                 ++number_of_errors;
471             }
472         }
473 
474         //
475         // make sure the difference file exists and has not been altered
476         //
477         if (diff_file_required)
478         {
479             path_d = str_format("%s,D", path->str_text);
480             if (!c_src_data->diff_file_fp)
481             {
482                 c_src_data->diff_file_fp =
483                     (fingerprint_ty *)fingerprint_type.alloc();
484             }
485             cid.get_up()->become_begin();
486             same_d =
487                 change_fingerprint_same(c_src_data->diff_file_fp, path_d, 0);
488             cid.get_up()->become_end();
489             trace(("same_d = %d\n", same_d));
490             str_free(path_d);
491 
492             if
493             (
494                 !c_src_data->diff_file_fp
495             ||
496                 !c_src_data->diff_file_fp->youngest
497             )
498             {
499                 scp = sub_context_new();
500                 sub_var_set_format
501                 (
502                     scp,
503                     "File_Name",
504                     "%s,D",
505                     c_src_data->file_name->str_text
506                 );
507                 change_error(cid.get_cp(), scp, i18n("$filename not found"));
508                 sub_context_delete(scp);
509                 ++number_of_errors;
510             }
511         }
512         else
513             same_d = 1;
514         str_free(path);
515 
516         //
517         // check that a difference has been done,
518         // and that no files have been modified since.
519         //
520         if (!diff_whine && diff_file_required)
521         {
522             if (file_required && !same && c_src_data->file_fp->oldest)
523             {
524                 scp = sub_context_new();
525                 sub_var_set_string(scp, "File_Name", c_src_data->file_name);
526                 change_error
527                 (
528                     cid.get_cp(),
529                     scp,
530                     i18n("$filename changed after diff")
531                 );
532                 sub_context_delete(scp);
533                 diff_whine++;
534                 ++number_of_errors;
535             }
536             else if (!same_d && c_src_data->diff_file_fp->youngest)
537             {
538                 scp = sub_context_new();
539                 sub_var_set_format
540                 (
541                     scp,
542                     "File_Name",
543                     "%s,D",
544                     c_src_data->file_name->str_text
545                 );
546                 change_error
547                 (
548                     cid.get_cp(),
549                     scp,
550                     i18n("$filename changed after diff")
551                 );
552                 sub_context_delete(scp);
553                 diff_whine++;
554                 ++number_of_errors;
555             }
556             else if ((file_required && !same) || !same_d)
557             {
558                 change_error(cid.get_cp(), 0, i18n("diff required"));
559                 diff_whine++;
560                 ++number_of_errors;
561             }
562         }
563 
564         // for checking, we need the p_src *before* shallowing
565         fstate_src_ty *p_src_orig =
566             cid.get_pp()->file_find(c_src_data->file_name, view_path_none);
567         trace(("p_src_orig = %p\n", p_src_orig));
568 
569         //
570         // For each change file that is acting on a project file
571         // from a deeper level than the immediate parent
572         // project, the file needs to be added to the immediate
573         // parent project.
574         //
575         // This is where the about_to_be_copied_by attribute comes from.
576         // Nothing is done for files being created.
577         //
578         trace(("shallowing \"%s\"\n", c_src_data->file_name->str_text));
579         project_file_shallow
580         (
581             cid.get_pp(),
582             c_src_data->file_name,
583             magic_zero_encode(cid.get_change_number())
584         );
585 
586         //
587         // Find the project after "shallowing" it, as this
588         // gives the project file on the immediate branch,
589         // rather than deeper down the family tree.
590         //
591         p_src_data =
592             cid.get_pp()->file_find(c_src_data->file_name, view_path_none);
593         trace(("p_src_data = %p\n", p_src_data));
594         assert(!p_src_data == !p_src_orig);
595 
596         //
597         // It is an error if any files in the change
598         // file table have different edit numbers to the
599         // baseline file table edit numbers.
600         //
601         trace(("c_src_data->action = %s\n",
602             file_action_ename(c_src_data->action)));
603         trace(("c_src_data->usage = %s\n",
604             file_usage_ename(c_src_data->usage)));
605         switch (c_src_data->action)
606         {
607         case file_action_remove:
608             //
609             // file being removed
610             //
611             if
612             (
613                 !is_a_branch
614             &&
615                 !cid.get_pp()->file_find
616                 (
617                     c_src_data->file_name,
618                     view_path_extreme
619                 )
620             )
621             {
622                 scp = sub_context_new();
623                 sub_var_set_string(scp, "File_Name", c_src_data->file_name);
624                 change_error
625                 (
626                     cid.get_cp(),
627                     scp,
628                     i18n("no $filename in baseline")
629                 );
630                 sub_context_delete(scp);
631                 ++number_of_errors;
632             }
633 
634             //
635             // Make sure the file exists in the project.
636             //
637             // This can happen for aede on branches which
638             // have had a new file created and then deleted;
639             // thus the file will not exist in the branch's
640             // project.
641             //
642             if (!p_src_data)
643             {
644                 p_src_data = cid.get_pp()->file_new(c_src_data);
645                 p_src_data->action = file_action_transparent;
646                 p_src_data->about_to_be_created_by = cid.get_change_number();
647                 assert(c_src_data->edit || c_src_data->edit_origin);
648                 p_src_data->edit =
649                     history_version_copy
650                     (
651                         c_src_data->edit
652                     ?
653                         c_src_data->edit
654                     :
655                         c_src_data->edit_origin
656                     );
657                 p_src_data->edit_origin =
658                     history_version_copy(p_src_data->edit);
659                 break;
660             }
661 
662             //
663             // Make sure the edit numbers match.  If it
664             // matches any of the ancestral edits, it does
665             // not require a merge.
666             //
667             if
668             (
669                 file_required
670             &&
671                 !change_file_up_to_date(cid.get_pp(), c_src_data)
672             )
673             {
674                 scp = sub_context_new();
675                 sub_var_set_string(scp, "File_Name", c_src_data->file_name);
676                 if (is_a_branch)
677                 {
678                     change_error
679                     (
680                         cid.get_cp(),
681                         scp,
682                         i18n("baseline $filename changed, merge in new change")
683                     );
684                 }
685                 else
686                 {
687                     change_error
688                     (
689                         cid.get_cp(),
690                         scp,
691                         i18n("baseline $filename changed")
692                     );
693                 }
694                 sub_context_delete(scp);
695                 ++number_of_errors;
696             }
697 
698             //
699             // make sure we can lock the file
700             //
701             if (p_src_data->locked_by)
702             {
703                 scp = sub_context_new();
704                 sub_var_set_string(scp, "File_Name", c_src_data->file_name);
705                 sub_var_set_long(scp, "Number", p_src_data->locked_by);
706                 change_error
707                 (
708                     cid.get_cp(),
709                     scp,
710                     i18n("file \"$filename\" locked for change $number")
711                 );
712                 sub_context_delete(scp);
713                 ++number_of_errors;
714             }
715             else
716                 p_src_data->locked_by = cid.get_change_number();
717             break;
718 
719         case file_action_transparent:
720             //
721             // Do absolutely nothing for transparent branch files.
722             //
723             if (cid.get_cp()->was_a_branch())
724                 break;
725             // fall through...
726 
727         case file_action_modify:
728             //
729             // file being modified
730             //
731             if
732             (
733                 !is_a_branch
734             &&
735                 !cid.get_pp()->file_find
736                 (
737                     c_src_data->file_name,
738                     view_path_extreme
739                 )
740             )
741             {
742                 scp = sub_context_new();
743                 sub_var_set_string(scp, "File_Name", c_src_data->file_name);
744                 change_error
745                 (
746                     cid.get_cp(),
747                     scp,
748                     i18n("no $filename in baseline")
749                 );
750                 sub_context_delete(scp);
751                 ++number_of_errors;
752                 continue;
753             }
754 
755             //
756             // Make sure the file exists in the project.
757             //
758             // This can happen for aede on branches which
759             // have had a new file created and then modified;
760             // thus the file will not exist in the branch's
761             // project.
762             //
763             if (!p_src_data)
764             {
765                 p_src_data = cid.get_pp()->file_new(c_src_data);
766                 p_src_data->action = file_action_transparent;
767                 p_src_data->about_to_be_created_by = cid.get_change_number();
768                 assert(c_src_data->edit || c_src_data->edit_origin);
769                 p_src_data->edit =
770                     history_version_copy
771                     (
772                         c_src_data->edit
773                     ?
774                         c_src_data->edit
775                     :
776                         c_src_data->edit_origin
777                     );
778                 p_src_data->edit_origin =
779                     history_version_copy(p_src_data->edit);
780                 break;
781             }
782 
783             //
784             // Make sure the edit numbers match.  If it
785             // matches any of the ancestral edits, it does
786             // not require a merge.
787             //
788             if (!change_file_up_to_date(cid.get_pp(), c_src_data))
789             {
790                 switch (c_src_data->usage) {
791                 //
792                 // Build files can't be merged so they must pass
793                 // anyway to the upper branch.
794                 //
795                 case file_usage_build:
796                     break;
797 
798                 case file_usage_config:
799                 case file_usage_source:
800                 case file_usage_test:
801                 case file_usage_manual_test:
802 #ifndef DEBUG
803                 default:
804 #endif
805                     scp = sub_context_new();
806                     sub_var_set_string(scp, "File_Name", c_src_data->file_name);
807                     change_error
808                     (
809                         cid.get_cp(),
810                         scp,
811                         i18n("baseline $filename changed")
812                     );
813                     sub_context_delete(scp);
814                     ++number_of_errors;
815                     break;
816                 }
817             }
818 
819             //
820             // make sure we can lock the file
821             //
822             if (p_src_data->locked_by)
823             {
824                 scp = sub_context_new();
825                 sub_var_set_string(scp, "File_Name", c_src_data->file_name);
826                 sub_var_set_long(scp, "Number", p_src_data->locked_by);
827                 change_error
828                 (
829                     cid.get_cp(),
830                     scp,
831                     i18n("file \"$filename\" locked for change $number")
832                 );
833                 sub_context_delete(scp);
834                 ++number_of_errors;
835             }
836             else
837                 p_src_data->locked_by = cid.get_change_number();
838             break;
839 
840         case file_action_create:
841             //
842             // file being created
843             //
844             if (p_src_data)
845             {
846                 trace(("p_src_data->action = %s\n",
847                     file_action_ename(p_src_data->action)));
848                 trace(("p_src_orig->action = %s\n",
849                     file_action_ename(p_src_orig->action)));
850                 if (p_src_data->about_to_be_created_by)
851                 {
852                     scp = sub_context_new();
853                     sub_var_set_string(scp, "File_Name", c_src_data->file_name);
854                     sub_var_set_long
855                     (
856                         scp,
857                         "Number",
858                         c_src_data->about_to_be_created_by
859                     );
860                     change_error
861                     (
862                         cid.get_cp(),
863                         scp,
864                         i18n("file \"$filename\" locked for change $number")
865                     );
866                     sub_context_delete(scp);
867                     ++number_of_errors;
868                 }
869                 else if (p_src_orig->action == file_action_remove)
870                 {
871                     // OK.
872                     trace(("let it slide\n"));
873                 }
874                 else
875                 {
876                     // if usage==build, no logical conflict
877                     if (p_src_data->usage != file_usage_build)
878                     {
879                         // Note: it doesn't matter if we are changing it
880                         // to something other than file_usage_build.
881                         file_already_in_baseline:
882                         sub_context_ty sc;
883                         sc.var_set_string("File_Name", c_src_data->file_name);
884                         change_error
885                         (
886                             cid.get_cp(),
887                             &sc,
888                             i18n("$filename in baseline")
889                         );
890                         ++number_of_errors;
891                         break;
892                     }
893                     else
894                     {
895                         switch (c_src_data->action)
896                         {
897                         case file_action_create:
898                             c_src_data->action = file_action_modify;
899                             break;
900 
901                         case file_action_modify:
902                             break;
903 
904                         default:
905                             goto file_already_in_baseline;
906                         }
907                     }
908                 }
909             }
910             else
911             {
912                 //
913                 // add a new entry to the pstate src list,
914                 // and mark it as "about to be created".
915                 //
916                 p_src_data = cid.get_pp()->file_new(c_src_data);
917                 p_src_data->action = file_action_transparent;
918                 p_src_data->about_to_be_created_by = cid.get_change_number();
919             }
920             p_src_data->locked_by = cid.get_change_number();
921             break;
922 
923         case file_action_insulate:
924             //
925             // There should be no insulation files in the
926             // change at develop end time.  This is because
927             // if the change is still being insulated from
928             // the baseline, something could easily be
929             // overlooked.
930             //
931             scp = sub_context_new();
932             sub_var_set_string(scp, "File_Name", c_src_data->file_name);
933             change_error(cid.get_cp(), scp, i18n("$filename is insulation"));
934             sub_context_delete(scp);
935             ++number_of_errors;
936             break;
937         }
938     }
939 
940     //
941     // If all the files in this change set are marked entire-source-hide
942     // we mark the change set with the aeget:inventory:hide attribute,
943     // unless the user has already done so.
944     //
945     if
946     (
947         (entire_source_hide || local_source_hide)
948     &&
949         /* see lib/en/man1/aeca.1 */
950         !change_attributes_find_boolean(cid.get_cp(), "aeget:inventory:hide")
951     )
952     {
953         change_attributes_append(cstate_data, "aeget:inventory:hide", "true");
954     }
955 
956     //
957     // It is an error if this change is a branch, and there are any
958     // changes outstanding on the branch.
959     //
960     if (is_a_branch)
961         project_active_check_branch(cid.get_cp(), 0);
962 
963     //
964     // if the config file changes,
965     // make sure the project file names still conform
966     //
967     if (config_seen)
968     {
969         for (j = 0;; ++j)
970         {
971             fstate_src_ty   *p_src_data;
972             string_ty       *e;
973 
974             p_src_data = cid.get_pp()->file_nth(j, view_path_extreme);
975             if (!p_src_data)
976                 break;
977             if
978             (
979                 cid.get_cp()->file_find
980                 (
981                     nstring(p_src_data->file_name),
982                     view_path_first
983                 )
984             )
985                 continue;
986 
987             e = change_filename_check(cid.get_cp(), p_src_data->file_name);
988             if (e)
989             {
990                 scp = sub_context_new();
991                 sub_var_set_string(scp, "MeSsaGe", e);
992                 str_free(e);
993                 change_error(cid.get_cp(), scp, i18n("project $message"));
994                 sub_context_delete(scp);
995                 ++number_of_errors;
996             }
997         }
998     }
999 
1000     //
1001     // verify that the youngest file is older than the
1002     // build, test, test -bl and test -reg times
1003     //
1004     trace(("build required = %s\n",
1005                 (change_build_required(cid.get_cp()) ? "true" : "false")));
1006     if
1007     (
1008         change_build_required(cid.get_cp())
1009     &&
1010         (
1011             !cstate_data->build_time
1012         ||
1013             (
1014                 os_unthrottle()
1015             ?
1016                 youngest > cstate_data->build_time
1017             :
1018                 youngest >= cstate_data->build_time
1019             )
1020         )
1021     )
1022     {
1023         trace(("mark\n"));
1024         if (youngest_name && cstate_data->build_time)
1025         {
1026             scp = sub_context_new();
1027             sub_var_set_charstar
1028             (
1029                 scp,
1030                 "Outstanding",
1031                 change_outstanding_builds(cid.get_cp(), youngest)
1032             );
1033             sub_var_optional(scp, "Outstanding");
1034             sub_var_set_string(scp, "File_Name", youngest_name);
1035             change_error
1036             (
1037                 cid.get_cp(),
1038                 scp,
1039                 i18n("$filename changed after build")
1040             );
1041             sub_context_delete(scp);
1042         }
1043         else
1044         {
1045             scp = sub_context_new();
1046             sub_var_set_charstar
1047             (
1048                 scp,
1049                 "Outstanding",
1050                 change_outstanding_builds(cid.get_cp(), youngest)
1051             );
1052             sub_var_optional(scp, "Outstanding");
1053             change_error(cid.get_cp(), scp, i18n("build required"));
1054             sub_context_delete(scp);
1055         }
1056         ++number_of_errors;
1057     }
1058     if
1059     (
1060         !cstate_data->test_exempt
1061     &&
1062         (
1063             !cstate_data->test_time
1064         ||
1065             (
1066                 os_unthrottle()
1067             ?
1068                 youngest > cstate_data->test_time
1069             :
1070                 youngest >= cstate_data->test_time
1071             )
1072         )
1073     )
1074     {
1075         scp = sub_context_new();
1076         sub_var_set_charstar
1077         (
1078             scp,
1079             "Outstanding",
1080             change_outstanding_tests(cid.get_cp(), youngest)
1081         );
1082         sub_var_optional(scp, "Outstanding");
1083         change_error(cid.get_cp(), scp, i18n("test required"));
1084         sub_context_delete(scp);
1085         ++number_of_errors;
1086     }
1087     if
1088     (
1089         !cstate_data->test_baseline_exempt
1090     &&
1091         (
1092             !cstate_data->test_baseline_time
1093         ||
1094             (
1095                 os_unthrottle()
1096             ?
1097                 youngest > cstate_data->test_baseline_time
1098             :
1099                 youngest >= cstate_data->test_baseline_time
1100             )
1101         )
1102     )
1103     {
1104         scp = sub_context_new();
1105         sub_var_set_charstar
1106         (
1107             scp,
1108             "Outstanding",
1109             change_outstanding_tests_baseline(cid.get_cp(), youngest)
1110         );
1111         sub_var_optional(scp, "Outstanding");
1112         change_error(cid.get_cp(), scp, i18n("test -bl required"));
1113         sub_context_delete(scp);
1114         ++number_of_errors;
1115     }
1116     if
1117     (
1118         !cstate_data->regression_test_exempt
1119     &&
1120         (
1121             !cstate_data->regression_test_time
1122         ||
1123             (
1124                 os_unthrottle()
1125             ?
1126                 youngest > cstate_data->regression_test_time
1127             :
1128                 youngest >= cstate_data->regression_test_time
1129             )
1130         )
1131     )
1132     {
1133         scp = sub_context_new();
1134         sub_var_set_charstar
1135         (
1136             scp,
1137             "Outstanding",
1138             change_outstanding_tests_regression(cid.get_cp(), youngest)
1139         );
1140         sub_var_optional(scp, "Outstanding");
1141         change_error(cid.get_cp(), scp, i18n("test -reg required"));
1142         sub_context_delete(scp);
1143         ++number_of_errors;
1144     }
1145 
1146     //
1147     // if there was any problem,
1148     // stay in 'being developed' state.
1149     //
1150     if (number_of_errors)
1151     {
1152         scp = sub_context_new();
1153         sub_var_set_long(scp, "Number", number_of_errors);
1154         sub_var_optional(scp, "Number");
1155         change_fatal(cid.get_cp(), scp, i18n("develop end fail"));
1156         sub_context_delete(scp);
1157     }
1158     dd = str_copy(change_development_directory_get(cid.get_cp(), 1));
1159     str_free(dd);
1160 
1161     //
1162     // As a last line of defence, run the develop_end_policy_command
1163     //
1164     change_run_develop_end_policy_command(cid.get_cp(), cid.get_up());
1165 
1166     //
1167     // add to history for state change
1168     // Advance the change to the being-reviewed state.
1169     //
1170     history_data = change_history_new(cid.get_cp(), cid.get_up());
1171     history_data->what = cstate_history_what_develop_end;
1172     cstate_data->state = cstate_state_being_reviewed;
1173     switch (project_develop_end_action_get(cid.get_pp()))
1174     {
1175     case cstate_branch_develop_end_action_goto_being_reviewed:
1176         history_data->what = cstate_history_what_develop_end;
1177         cstate_data->state = cstate_state_being_reviewed;
1178         break;
1179 
1180     case cstate_branch_develop_end_action_goto_awaiting_review:
1181         history_data->what = cstate_history_what_develop_end_2ar;
1182         cstate_data->state = cstate_state_awaiting_review;
1183         break;
1184 
1185     case cstate_branch_develop_end_action_goto_awaiting_integration:
1186         history_data->what = cstate_history_what_develop_end_2ai;
1187         cstate_data->state = cstate_state_awaiting_integration;
1188         break;
1189     }
1190     if (up_admin)
1191     {
1192         string_ty *r2 =
1193             str_format
1194             (
1195                 "Forced by administrator %s.",
1196                 up_admin->name().quote_c().c_str()
1197             );
1198         if (reason)
1199         {
1200             string_ty *r1 = reason;
1201             reason = str_format("%s\n%s", r1->str_text, r2->str_text);
1202             str_free(r1);
1203             str_free(r2);
1204         }
1205         else
1206             reason = r2;
1207     }
1208     history_data->why = reason;
1209 
1210     //
1211     // It is tempting to call
1212     //
1213     //     change_build_times_clear(cid.get_cp());
1214     //
1215     // at this point, but we don't.  This is so that in the event
1216     // of an aedeu (to get out of the way of another change, maybe)
1217     // a full build-and-test cycle is only required of they actually
1218     // edit something.
1219     //
1220 
1221     //
1222     // Remove the change from the list of assigned changes in the user
1223     // change table (in the user row).
1224     //
1225     cid.get_up()->own_remove(cid.get_pp(), cid.get_change_number());
1226 
1227     //
1228     // Make the development directory read only.
1229     //
1230     // This is actually conditional upon project_protect_development_
1231     // directory_get(cid.get_pp()) but the test is inside the change_
1232     // development_directory_chmod_read_only(cid.get_cp()) function, because it
1233     // also makes sure the source files are readable by the reviewers.
1234     //
1235     change_development_directory_chmod_read_only(cid.get_cp());
1236 
1237     //
1238     // If the project is configured to use Signed-off-by lines in
1239     // change descriptions, append a Signed-off-line to this change's
1240     // description.
1241     //
1242     // If the change has a UUID, it means that it almost certainly
1243     // arrived via aedist or aepatch.  This means that the change
1244     // (a) was signed off by the sender of the change set and (b)
1245     // the receiver of the change set has not edited it in any way;
1246     // therefore there is no need for *this* user to sign off.
1247     //
1248     if
1249     (
1250         cstate_data->uuid
1251     ?
1252         option_signed_off_by_get(false)
1253     :
1254         change_signed_off_by_get(cid.get_cp())
1255     )
1256         change_signed_off_by(cid.get_cp(), cid.get_up());
1257 
1258     //
1259     // Write the change table row.
1260     // Write the user table row.
1261     // Release advisory locks.
1262     //
1263     cid.get_cp()->cstate_write();
1264     cid.get_pp()->pstate_write();
1265     cid.get_up()->ustate_write();
1266     commit();
1267     lock_release();
1268 
1269     //
1270     // run the notify command
1271     //
1272     switch (project_develop_end_action_get(cid.get_pp()))
1273     {
1274 #ifndef DEBUG
1275     default:
1276 #endif
1277     case cstate_branch_develop_end_action_goto_being_reviewed:
1278     case cstate_branch_develop_end_action_goto_awaiting_review:
1279         cid.get_cp()->run_develop_end_notify_command();
1280         break;
1281 
1282     case cstate_branch_develop_end_action_goto_awaiting_integration:
1283         cid.get_cp()->run_review_pass_notify_command();
1284         break;
1285     }
1286 
1287     //
1288     // Update the RSS feed file if necessary.
1289     //
1290     rss_add_item_by_change(cid.get_pp(), cid.get_cp());
1291 
1292     //
1293     // verbose success message
1294     //
1295     change_verbose(cid.get_cp(), 0, i18n("development completed"));
1296     trace(("}\n"));
1297 }
1298 
1299 
1300 void
develop_end(void)1301 develop_end(void)
1302 {
1303     static arglex_dispatch_ty dispatch[] =
1304     {
1305         { arglex_token_help, develop_end_help, 0 },
1306         { arglex_token_list, develop_end_list, 0 },
1307     };
1308 
1309     trace(("develop_end()\n{\n"));
1310     arglex_dispatch(dispatch, SIZEOF(dispatch), develop_end_main);
1311     trace(("}\n"));
1312 }
1313 
1314 
1315 // vim: set ts=8 sw=4 et :
1316