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