1 //
2 // aegis - project change supervisor
3 // Copyright (C) 1991-2009, 2011, 2012 Peter Miller
4 // Copyright (C) 2008, 2010 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/unistd.h>
26 #include <common/ac/sys/types.h>
27 #include <common/ac/sys/stat.h>
28
29 #include <common/gmatch.h>
30 #include <common/nstring.h>
31 #include <common/progname.h>
32 #include <common/quit.h>
33 #include <common/sizeof.h>
34 #include <common/trace.h>
35 #include <common/uuidentifier.h>
36 #include <libaegis/ael/change/by_state.h>
37 #include <libaegis/arglex/change.h>
38 #include <libaegis/arglex/project.h>
39 #include <libaegis/arglex2.h>
40 #include <libaegis/change.h>
41 #include <libaegis/change/attributes.h>
42 #include <libaegis/change/branch.h>
43 #include <libaegis/change/file.h>
44 #include <libaegis/change/identifier.h>
45 #include <libaegis/commit.h>
46 #include <libaegis/dir.h>
47 #include <libaegis/file.h>
48 #include <libaegis/help.h>
49 #include <libaegis/lock.h>
50 #include <libaegis/log.h>
51 #include <libaegis/os.h>
52 #include <libaegis/project.h>
53 #include <libaegis/project/file.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/aeib.h>
61
62
63 static void
integrate_begin_usage(void)64 integrate_begin_usage(void)
65 {
66 const char *progname;
67
68 progname = progname_get();
69 fprintf(stderr, "usage: %s -Integrate_Begin [ <option>... ]\n", progname);
70 fprintf
71 (
72 stderr,
73 " %s -Integrate_Begin -List [ <option>... ]\n",
74 progname
75 );
76 fprintf(stderr, " %s -Integrate_Begin -Help\n", progname);
77 quit(1);
78 }
79
80
81 static void
integrate_begin_help(void)82 integrate_begin_help(void)
83 {
84 help("aeib", integrate_begin_usage);
85 }
86
87
88 static void
integrate_begin_list(void)89 integrate_begin_list(void)
90 {
91 trace(("integrate_begin_list()\n{\n"));
92 arglex();
93 change_identifier cid;
94 cid.command_line_parse_rest(integrate_begin_usage);
95 list_changes_in_state_mask(cid, 1 << cstate_state_awaiting_integration);
96 trace(("}\n"));
97 }
98
99
100 static bool
ends_with_comma_d(string_ty * s)101 ends_with_comma_d(string_ty *s)
102 {
103 if (s->str_length < 2)
104 return false;
105
106 char *cp = s->str_text + s->str_length - 2;
107 if (cp[0] == ',' && cp[1] == 'D')
108 return true;
109
110 return false;
111 }
112
113
114 static string_ty *
remove_comma_d_if_present(string_ty * s)115 remove_comma_d_if_present(string_ty *s)
116 {
117 if (ends_with_comma_d(s))
118 return str_n_from_c(s->str_text, s->str_length - 2);
119
120 return str_copy(s);
121 }
122
123
124 static int
isa_suppressed_filename(change::pointer cp,string_ty * fn)125 isa_suppressed_filename(change::pointer cp, string_ty *fn)
126 {
127 pconf_ty *pconf_data;
128 pconf_integrate_begin_exceptions_list_ty *p;
129 size_t j;
130
131 pconf_data = change_pconf_get(cp, 1);
132 p = pconf_data->integrate_begin_exceptions;
133 if (!p)
134 return 0;
135 for (j = 0; j < p->length; ++j)
136 {
137 if (gmatch(p->list[j]->str_text, fn->str_text))
138 return 1;
139 }
140 return 0;
141 }
142
143
144 static void
chmod_common(string_ty * filename,const struct stat * st,int rdwr,change::pointer)145 chmod_common(string_ty *filename, const struct stat *st, int rdwr,
146 change::pointer )
147 {
148 int mode;
149
150 if (rdwr)
151 {
152 //
153 // If it is not a source file, it could be owned
154 // by some other user, and we have no control
155 // over its owner or mode. Report a warning if
156 // we can't change the mode.
157 //
158 // Also, we leave it writable if it is already.
159 // This is normal for generated files.
160 //
161 mode = (st->st_mode | 0644) & ~0022;
162 }
163 else
164 {
165 //
166 // Source files, on the other hand, should always
167 // be owned by us, and thus always chmod(2)able
168 // by us. Have a hissy-fit if they aren't.
169 //
170 // Also, source files should be read-only.
171 //
172 mode = (st->st_mode | 0444) & ~0222;
173 }
174 os_chmod(filename, mode);
175 }
176
177
178 static void
link_tree_callback_minimum(void * arg,dir_walk_message_ty message,string_ty * path,const struct stat * st)179 link_tree_callback_minimum(void *arg, dir_walk_message_ty message,
180 string_ty *path, const struct stat *st)
181 {
182 string_ty *s1;
183 string_ty *s1short;
184 string_ty *s2;
185 change::pointer cp;
186 fstate_src_ty *src;
187 int exists;
188
189 trace(("link_tree_callback_minimum(message = %d, path = %08lX, "
190 "st = %08lX)\n{\n", message, (long)path, (long)st));
191 os_interrupt_cope();
192 cp = (change::pointer )arg;
193 assert(cp);
194 trace_string(path->str_text);
195 s1 = os_below_dir(cp->pp->baseline_path_get(true), path);
196 assert(s1);
197 trace_string(s1->str_text);
198 if (!s1->str_length)
199 s2 = str_copy(change_integration_directory_get(cp, 1));
200 else
201 s2 = os_path_join(change_integration_directory_get(cp, 1), s1);
202 trace_string(s2->str_text);
203 switch (message)
204 {
205 case dir_walk_dir_before:
206 if (!s1->str_length)
207 {
208 assert(!os_exists(s2));
209 os_mkdir(s2, 02755);
210 }
211 break;
212
213 case dir_walk_file:
214 if (st->st_mode & 07000)
215 {
216 //
217 // Don't link files with horrible modes.
218 // They shouldn't be source, anyway.
219 //
220 project_become_undo(cp->pp);
221 exists = !!cp->pp->file_find(s1, view_path_extreme);
222 project_become(cp->pp);
223 if (!exists)
224 break;
225 }
226
227 //
228 // Remove the ,D suffix, if present, and use the
229 // shortened form to test for project and change
230 // membership. This ensures that diff files are also
231 // copied across. Note that deleted files *have*
232 // difference files.
233 //
234 s1short = remove_comma_d_if_present(s1);
235
236 //
237 // Don't link it if it's not a source file, or a
238 // relevant ,D file.
239 //
240 project_become_undo(cp->pp);
241 src = cp->pp->file_find(s1short, view_path_simple);
242 project_become(cp->pp);
243 if
244 (
245 !src
246 ||
247 (src->deleted_by && str_equal(s1, s1short))
248 )
249 {
250 str_free(s1short);
251 break;
252 }
253
254 //
255 // Don't link build "source" files. We are tracking the
256 // contents in history, is all, but they are build artifacts.
257 //
258 if (src && src->usage == file_usage_build)
259 {
260 str_free(s1short);
261 break;
262 }
263
264 //
265 // make sure the directory is there
266 //
267 os_mkdir_between(change_integration_directory_get(cp, 1), s1, 02755);
268
269 //
270 // Don't link a file (or the corresponding ,D file) if
271 // the file is in the change.
272 //
273 if (cp->file_find(nstring(s1short), view_path_first))
274 {
275 str_free(s1short);
276 break;
277 }
278
279 //
280 // Don't link a suppressed file.
281 // BUT keep primary source files and their diff files.
282 //
283 project_become_undo(cp->pp);
284 {
285 bool remove_the_file = false;
286 fstate_src_ty *psrc = cp->pp->file_find(s1short, view_path_simple);
287 if
288 (
289 !psrc
290 &&
291 !ends_with_comma_d(s1)
292 &&
293 isa_suppressed_filename(cp, s1short)
294 )
295 remove_the_file = true;
296
297 //
298 // Don't link build "source" files. We are tracking the
299 // contents in history, is all, but they are build artifacts.
300 //
301 if (psrc && psrc->usage == file_usage_build)
302 remove_the_file = true;
303 project_become(cp->pp);
304 str_free(s1short);
305 if (remove_the_file)
306 break;
307 }
308
309 //
310 // link the file and make sure it is a suitable mode
311 //
312 trace(("ln %s %s\n", path->str_text, s2->str_text));
313 os_link(path, s2);
314 project_become_undo(cp->pp);
315 exists = !!cp->pp->file_find(s1, view_path_simple);
316 project_become(cp->pp);
317 chmod_common(s2, st, !exists, cp);
318
319 //
320 // Update the modify time of the linked file. On a
321 // fully-functional Unix, this is unnecessary, because a
322 // hard link alters the ctime, not the mtime, and this
323 // is a no-op. Solaris, on the other hand, is brain dead.
324 //
325 os_mtime_set(s2, st->st_mtime);
326 break;
327
328 case dir_walk_dir_after:
329 break;
330
331 case dir_walk_special:
332 case dir_walk_symlink:
333 //
334 // ignore special files
335 //
336 // They could never be source files, so they must be
337 // created by the build. These ones must always be
338 // created at build time, that's all.
339 //
340 break;
341 }
342 str_free(s2);
343 str_free(s1);
344 trace(("}\n"));
345 }
346
347
348 static void
link_tree_callback(void * arg,dir_walk_message_ty message,string_ty * path,const struct stat * st)349 link_tree_callback(void *arg, dir_walk_message_ty message, string_ty *path,
350 const struct stat *st)
351 {
352 string_ty *s1;
353 string_ty *s1short;
354 string_ty *s2;
355 change::pointer cp;
356 fstate_src_ty *src;
357 string_ty *contents;
358
359 trace(("link_tree_callback(message = %d, path = %08lX, st = %08lX)\n{\n",
360 message, (long)path, (long)st));
361 os_interrupt_cope();
362 cp = (change::pointer )arg;
363 assert(cp);
364 trace_string(path->str_text);
365 s1 = os_below_dir(cp->pp->baseline_path_get(true), path);
366 assert(s1);
367 trace_string(s1->str_text);
368 if (!s1->str_length)
369 s2 = str_copy(change_integration_directory_get(cp, 1));
370 else
371 s2 = os_path_join(change_integration_directory_get(cp, 1), s1);
372 trace_string(s2->str_text);
373 switch (message)
374 {
375 case dir_walk_dir_before:
376 assert(!os_exists(s2));
377 os_mkdir(s2, 02755);
378 break;
379
380 case dir_walk_file:
381 project_become_undo(cp->pp);
382 src = cp->pp->file_find(s1, view_path_extreme);
383 project_become(cp->pp);
384 if (st->st_mode & 07000)
385 {
386 //
387 // Don't link files with horrible modes.
388 // They shouldn't be source, anyway.
389 //
390 if (!src)
391 break;
392 }
393
394 //
395 // Don't link a file (or the corresponding ,D file) if
396 // the file is in the change.
397 //
398 s1short = remove_comma_d_if_present(s1);
399 if (cp->file_find(nstring(s1short), view_path_first))
400 {
401 str_free(s1short);
402 break;
403 }
404
405 //
406 // Don't link a suppressed file.
407 // BUT keep primary source files and their diff files.
408 // (This is not -minimum, so "build" sources are OK.)
409 //
410 project_become_undo(cp->pp);
411 {
412 bool remove_the_file =
413 (
414 !cp->pp->file_find(s1short, view_path_simple)
415 &&
416 !ends_with_comma_d(s1)
417 &&
418 isa_suppressed_filename(cp, s1short)
419 );
420 project_become(cp->pp);
421 str_free(s1short);
422 if (remove_the_file)
423 break;
424 }
425
426 //
427 // link the file and make sure it is a suitable mode
428 //
429 os_link(path, s2);
430 chmod_common(s2, st, !src, cp);
431
432 //
433 // Update the modify time of the linked file. On a
434 // fully-functional Unix, this is unnecessary, because a
435 // hard link alters the ctime, not the mtime, and this
436 // is a no-op. Solaris, on the other hand, is brain dead.
437 //
438 os_mtime_set(s2, st->st_mtime);
439 break;
440
441 case dir_walk_dir_after:
442 break;
443
444 case dir_walk_symlink:
445 contents = os_readlink(path);
446 os_symlink(contents, s2);
447 str_free(contents);
448 break;
449
450 case dir_walk_special:
451 //
452 // ignore special files
453 //
454 // They could never be source files, so they must be
455 // created by the build. These ones must always be
456 // created at build time, that's all.
457 //
458 break;
459 }
460 str_free(s2);
461 str_free(s1);
462 trace(("}\n"));
463 }
464
465
466 static void
copy_tree_callback_minimum(void * arg,dir_walk_message_ty message,string_ty * path,const struct stat * st)467 copy_tree_callback_minimum(void *arg, dir_walk_message_ty message,
468 string_ty *path, const struct stat *st)
469 {
470 string_ty *s1;
471 string_ty *s1short;
472 string_ty *s2;
473 change::pointer cp;
474 fstate_src_ty *src;
475 int uid;
476 int exists;
477
478 trace(("copy_tree_callback_minimum(message = %d, path = %08lX, "
479 "st = %08lX)\n{\n", message, (long)path, (long)st));
480 os_interrupt_cope();
481 cp = (change::pointer )arg;
482 assert(cp);
483 trace_string(path->str_text);
484 s1 = os_below_dir(cp->pp->baseline_path_get(true), path);
485 assert(s1);
486 trace_string(s1->str_text);
487 if (!s1->str_length)
488 s2 = str_copy(change_integration_directory_get(cp, 1));
489 else
490 s2 = os_path_join(change_integration_directory_get(cp, 1), s1);
491 trace_string(s2->str_text);
492 switch (message)
493 {
494 case dir_walk_dir_before:
495 if (!s1->str_length)
496 {
497 assert(!os_exists(s2));
498 os_mkdir(s2, 02755);
499 }
500 break;
501
502 case dir_walk_file:
503 //
504 // Don't copy files which don't belong to us.
505 // Don't copy files with horrible modes.
506 // They shouldn't be source, anyway.
507 //
508 os_become_query(&uid, (int *)0, (int *)0);
509 if
510 (
511 ((unsigned int)st->st_uid != (unsigned int)uid)
512 ||
513 (st->st_mode & 07000) != 0
514 )
515 {
516 project_become_undo(cp->pp);
517 exists = !!cp->pp->file_find(s1, view_path_extreme);
518 project_become(cp->pp);
519 if (!exists)
520 break;
521 }
522
523 //
524 // Remove the ,D suffix, if present, and use the
525 // shortened form to test for project and change
526 // membership. This ensures that diff files are also
527 // copied across. Note that deleted files *have*
528 // difference files.
529 //
530 s1short = remove_comma_d_if_present(s1);
531 trace_string(s1short->str_text);
532
533 //
534 // Don't copy it if it's not a source file, or a
535 // relevant ,D file.
536 //
537 project_become_undo(cp->pp);
538 src = cp->pp->file_find(s1short, view_path_simple);
539 {
540 bool remove_the_file =
541 (
542 !src
543 ||
544 (src->deleted_by && str_equal(s1, s1short))
545 );
546
547 //
548 // Don't link build "source" files. We are tracking the
549 // contents in history, is all, but they are build artifacts.
550 //
551 if (src && src->usage == file_usage_build)
552 remove_the_file = true;
553
554 project_become(cp->pp);
555 if (remove_the_file)
556 {
557 str_free(s1short);
558 break;
559 }
560 }
561
562 //
563 // make sure the directory is there
564 //
565 os_mkdir_between(change_integration_directory_get(cp, 1), s1, 02755);
566
567 //
568 // Don't copy a file (or the corresponding ,D file) if
569 // the file is in the change.
570 //
571 if (cp->file_find(nstring(s1short), view_path_first))
572 {
573 str_free(s1short);
574 break;
575 }
576
577 //
578 // Don't copy a suppressed file.
579 // BUT keep primary source files and their diff files.
580 //
581 project_become_undo(cp->pp);
582 {
583 bool remove_the_file =
584 (
585 !cp->pp->file_find(s1short, view_path_extreme)
586 &&
587 !ends_with_comma_d(s1)
588 &&
589 isa_suppressed_filename(cp, s1short)
590 );
591 project_become(cp->pp);
592 str_free(s1short);
593 if (remove_the_file)
594 break;
595 }
596
597 //
598 // copy the file
599 //
600 trace(("cp %s %s\n", path->str_text, s2->str_text));
601 copy_whole_file(path, s2, 1);
602 project_become_undo(cp->pp);
603 exists = !!cp->pp->file_find(s1, view_path_extreme);
604 project_become(cp->pp);
605 chmod_common(s2, st, !exists, cp);
606 break;
607
608 case dir_walk_dir_after:
609 break;
610
611 case dir_walk_special:
612 case dir_walk_symlink:
613 //
614 // ignore special files
615 //
616 // They could never be source files, so they must be
617 // created by the build. These ones must always be
618 // created at build time, that's all.
619 //
620 break;
621 }
622 str_free(s2);
623 str_free(s1);
624 trace(("}\n"));
625 }
626
627
628 static void
copy_tree_callback(void * arg,dir_walk_message_ty message,string_ty * path,const struct stat * st)629 copy_tree_callback(void *arg, dir_walk_message_ty message, string_ty *path,
630 const struct stat *st)
631 {
632 string_ty *s1;
633 string_ty *s1short;
634 string_ty *s2;
635 change::pointer cp;
636 int uid;
637 string_ty *contents;
638 int exists;
639
640 trace(("copy_tree_callback(message = %d, path = %08lX, st = %08lX)\n{\n",
641 message, (long)path, (long)st));
642 os_interrupt_cope();
643 cp = (change::pointer )arg;
644 assert(cp);
645 trace_string(path->str_text);
646 s1 = os_below_dir(cp->pp->baseline_path_get(true), path);
647 assert(s1);
648 trace_string(s1->str_text);
649 if (!s1->str_length)
650 s2 = str_copy(change_integration_directory_get(cp, 1));
651 else
652 s2 = os_path_join(change_integration_directory_get(cp, 1), s1);
653 trace_string(s2->str_text);
654 switch (message)
655 {
656 case dir_walk_dir_before:
657 assert(!os_exists(s2));
658 os_mkdir(s2, 02755);
659 break;
660
661 case dir_walk_file:
662 //
663 // Don't copy files which don't belong to us.
664 // Don't copy files with horrible modes.
665 // They shouldn't be source, anyway.
666 //
667 os_become_query(&uid, (int *)0, (int *)0);
668 if
669 (
670 (unsigned int)st->st_uid != (unsigned int)uid
671 ||
672 (st->st_mode & 07000) != 0
673 )
674 {
675 project_become_undo(cp->pp);
676 exists = !!cp->pp->file_find(s1, view_path_extreme);
677 project_become(cp->pp);
678 if (!exists)
679 break;
680 }
681
682 //
683 // Remove the ,D suffix, if present, and use the
684 // shortened form to test for project and change
685 // membership. This ensures that diff files are also
686 // copied across. Note that deleted files *have*
687 // difference files.
688 //
689 s1short = remove_comma_d_if_present(s1);
690 trace_string(s1short->str_text);
691
692 //
693 // Don't copy a file (or the corresponding ,D file) if
694 // the file is in the change.
695 //
696 if (cp->file_find(nstring(s1short), view_path_first))
697 {
698 str_free(s1short);
699 break;
700 }
701
702 //
703 // Don't copy a suppressed file.
704 // BUT keep primary source files and their diff files.
705 // (This is not -minimum, so "build" sources are OK.)
706 //
707 project_become_undo(cp->pp);
708 {
709 bool remove_the_file =
710 (
711 !cp->pp->file_find(s1short, view_path_extreme)
712 &&
713 !ends_with_comma_d(s1)
714 &&
715 isa_suppressed_filename(cp, s1short)
716 );
717 project_become(cp->pp);
718 str_free(s1short);
719 if (remove_the_file)
720 break;
721 }
722
723 //
724 // copy the file
725 //
726 copy_whole_file(path, s2, 1);
727 project_become_undo(cp->pp);
728 exists = !!cp->pp->file_find(s1, view_path_extreme);
729 project_become(cp->pp);
730 chmod_common(s2, st, !exists, cp);
731 break;
732
733 case dir_walk_dir_after:
734 break;
735
736 case dir_walk_symlink:
737 contents = os_readlink(path);
738 os_symlink(contents, s2);
739 str_free(contents);
740 break;
741
742 case dir_walk_special:
743 //
744 // ignore special files
745 //
746 // They could never be source files, so they must be
747 // created by the build. These ones must always be
748 // created at build time, that's all.
749 //
750 break;
751 }
752 str_free(s2);
753 str_free(s1);
754 trace(("}\n"));
755 }
756
757
758 static void
integrate_begin_main(void)759 integrate_begin_main(void)
760 {
761 string_ty *bl;
762 string_ty *dd;
763 string_ty *id;
764 pconf_ty *pconf_data;
765 cstate_ty *cstate_data;
766 int j;
767 cstate_history_ty *history_data;
768 bool minimum;
769 bool maximum;
770 string_ty *project_name;
771 project *pp;
772 long change_number;
773 change::pointer cp;
774 user_ty::pointer up;
775 user_ty::pointer pup;
776 int errs;
777 string_ty *s;
778 long other;
779 log_style_ty log_style;
780 string_ty *base;
781 int base_max;
782 string_ty *num;
783 int mode;
784
785 trace(("integrate_begin_main()\n{\n"));
786 arglex();
787 minimum = false;
788 maximum = false;
789 project_name = 0;
790 change_number = 0;
791 log_style = log_style_create_default;
792 string_ty *reason = 0;
793 while (arglex_token != arglex_token_eoln)
794 {
795 switch (arglex_token)
796 {
797 default:
798 generic_argument(integrate_begin_usage);
799 continue;
800
801 case arglex_token_change:
802 arglex();
803 // fall through...
804
805 case arglex_token_number:
806 arglex_parse_change
807 (
808 &project_name,
809 &change_number,
810 integrate_begin_usage
811 );
812 continue;
813
814 case arglex_token_minimum:
815 if (minimum)
816 duplicate_option(integrate_begin_usage);
817 minimum = true;
818 break;
819
820 case arglex_token_maximum:
821 if (maximum)
822 duplicate_option(integrate_begin_usage);
823 maximum = true;
824 break;
825
826 case arglex_token_project:
827 arglex();
828 // fall through...
829
830 case arglex_token_string:
831 arglex_parse_project(&project_name, integrate_begin_usage);
832 continue;
833
834 case arglex_token_nolog:
835 if (log_style == log_style_none)
836 duplicate_option(integrate_begin_usage);
837 log_style = log_style_none;
838 break;
839
840 case arglex_token_wait:
841 case arglex_token_wait_not:
842 user_ty::lock_wait_argument(integrate_begin_usage);
843 break;
844
845 case arglex_token_reason:
846 if (reason)
847 duplicate_option(integrate_begin_usage);
848 switch (arglex())
849 {
850 default:
851 option_needs_string(arglex_token_reason, integrate_begin_usage);
852 // NOTREACHED
853
854 case arglex_token_string:
855 case arglex_token_number:
856 reason = str_from_c(arglex_value.alv_string);
857 break;
858 }
859 break;
860 }
861 arglex();
862 }
863
864 //
865 // locate project data
866 //
867 if (!project_name)
868 {
869 nstring n = user_ty::create()->default_project();
870 project_name = str_copy(n.get_ref());
871 }
872 pp = project_alloc(project_name);
873 str_free(project_name);
874 pp->bind_existing();
875
876 //
877 // locate user data
878 //
879 up = user_ty::create();
880
881 //
882 // locate change data
883 //
884 if (!change_number)
885 change_number = up->default_change(pp);
886 trace_long(change_number);
887 cp = change_alloc(pp, change_number);
888 change_bind_existing(cp);
889
890 //
891 // lock the project, the change and the user
892 //
893 pp->pstate_lock_prepare();
894 change_cstate_lock_prepare(cp);
895 up->ustate_lock_prepare();
896 lock_take();
897 cstate_data = cp->cstate_get();
898 pconf_data = change_pconf_get(cp, 1);
899
900 //
901 // make sure they are allowed to
902 //
903 if (!project_integrator_query(pp, up->name()))
904 project_fatal(pp, 0, i18n("not an integrator"));
905 if (cstate_data->state != cstate_state_awaiting_integration)
906 change_fatal(cp, 0, i18n("bad ib state"));
907 if
908 (
909 !project_developer_may_integrate_get(pp)
910 &&
911 nstring(cp->developer_name()) == up->name()
912 )
913 change_fatal(cp, 0, i18n("developer may not integrate"));
914 if
915 (
916 !project_reviewer_may_integrate_get(pp)
917 &&
918 nstring(cp->reviewer_name()) == up->name()
919 )
920 change_fatal(cp, 0, i18n("reviewer may not integrate"));
921
922 //
923 // make sure only one integration at a time
924 // for each project
925 //
926 other = project_current_integration_get(pp);
927 if (other)
928 {
929 sub_context_ty *scp;
930
931 scp = sub_context_new();
932 sub_var_set_long(scp, "Number", magic_zero_decode(other));
933 project_fatal(pp, scp, i18n("currently integrating $number"));
934 // NOTREACHED
935 sub_context_delete(scp);
936 }
937 trace_long(change_number);
938 project_current_integration_set(pp, change_number);
939
940 //
941 // Look for additional integration hints from the change set.
942 //
943 if (!minimum && !maximum)
944 {
945 static string_ty *integrate_begin_hint;
946 if (!integrate_begin_hint)
947 integrate_begin_hint = str_from_c("integrate-begin-hint");
948 /* see lib/en/man1/aeca.1 */
949 string_ty *vp = change_attributes_find(cp, integrate_begin_hint);
950 if (vp)
951 {
952 nstring value(vp);
953 if (value == "minimum")
954 minimum = true;
955 if (value == "maximum")
956 maximum = true;
957 }
958 }
959
960 //
961 // grab a delta number
962 // and advance the project's delta counter
963 //
964 cstate_data->delta_number = project_next_delta_number(pp);
965 cstate_data->delta_uuid = universal_unique_identifier();
966
967 //
968 // include the current year in the copyright_years field
969 //
970 change_copyright_years_now(cp);
971
972 //
973 // Create the integration directory.
974 //
975 base = str_format("delta%d", (int)getpid());
976 num = str_format(".%3.3ld", cstate_data->delta_number);
977 os_become_orig();
978 base_max = os_pathconf_name_max(project_top_path_get(pp, 0));
979 os_become_undo();
980 base_max -= num->str_length;
981 if (base_max < 5)
982 base_max = 5;
983 s =
984 str_format
985 (
986 "%s/%.*s%s",
987 project_top_path_get(pp, 0)->str_text,
988 base_max,
989 base->str_text,
990 num->str_text
991 );
992 str_free(base);
993 str_free(num);
994 change_integration_directory_set(cp, s);
995 str_free(s);
996
997 //
998 // There will be many files in the baseline in addition to the
999 // sources files.
1000 //
1001 // If any files are being deleted, only copy the source files
1002 // from the baseline to the integration directory. This way
1003 // the additional files relating to the removed sources file
1004 // are also removed (eg remove a .c file and you need to get
1005 // rid of the .o file). It also shows when dependencies have
1006 // become out-of-date.
1007 //
1008 // It is possible to ask for this from the command line, too.
1009 //
1010 if (maximum && minimum)
1011 {
1012 mutually_exclusive_options
1013 (
1014 arglex_token_minimum,
1015 arglex_token_maximum,
1016 integrate_begin_usage
1017 );
1018 }
1019 if (!maximum && !minimum)
1020 {
1021 for (j = 0;; ++j)
1022 {
1023 fstate_src_ty *src_data;
1024
1025 src_data = change_file_nth(cp, j, view_path_first);
1026 if (!src_data)
1027 break;
1028 switch (src_data->action)
1029 {
1030 case file_action_create:
1031 case file_action_modify:
1032 break;
1033
1034 case file_action_insulate:
1035 assert(0);
1036 break;
1037
1038 case file_action_remove:
1039 case file_action_transparent:
1040 minimum = true;
1041 break;
1042 }
1043 }
1044 }
1045
1046 //
1047 // Remember the minimum flag for the build command,
1048 // and also the aeipass command.
1049 //
1050 cstate_data->minimum_integration = minimum;
1051
1052 //
1053 // before creating the integration directory,
1054 // make sure none of the change files have been tampered with.
1055 //
1056 // (a) the project owner must have access
1057 // (b) the changed file must exist and not have been modified
1058 // (c) the difference file must exist and not have been modified
1059 //
1060 dd = change_development_directory_get(cp, 1);
1061 id = change_integration_directory_get(cp, 1);
1062 bl = pp->baseline_path_get(true);
1063 errs = 0;
1064 for (j = 0;; ++j)
1065 {
1066 fstate_src_ty *src_data;
1067 string_ty *s1;
1068 string_ty *s2;
1069 int ok;
1070
1071 src_data = change_file_nth(cp, j, view_path_first);
1072 if (!src_data)
1073 break;
1074 switch (src_data->usage)
1075 {
1076 case file_usage_source:
1077 case file_usage_config:
1078 case file_usage_test:
1079 case file_usage_manual_test:
1080 break;
1081
1082 case file_usage_build:
1083 continue;
1084 }
1085 switch (src_data->action)
1086 {
1087 case file_action_create:
1088 case file_action_modify:
1089 break;
1090
1091 case file_action_insulate:
1092 assert(0);
1093 // fall through...
1094
1095 case file_action_transparent:
1096 case file_action_remove:
1097 continue;
1098 }
1099
1100 s1 = cp->file_path(src_data->file_name);
1101 project_become(pp);
1102 ok = change_fingerprint_same(src_data->file_fp, s1, 0);
1103 project_become_undo(pp);
1104 if (!ok)
1105 {
1106 sub_context_ty *scp;
1107
1108 scp = sub_context_new();
1109 sub_var_set_string(scp, "File_Name", src_data->file_name);
1110 change_error(cp, scp, i18n("$filename altered"));
1111 sub_context_delete(scp);
1112 errs++;
1113 }
1114 assert(!src_data->file_fp || src_data->file_fp->youngest > 0);
1115 assert(!src_data->file_fp || src_data->file_fp->oldest > 0);
1116
1117 if (change_diff_required(cp))
1118 {
1119 //
1120 // Why doesn't this code follow the same logic about
1121 // diff_file_required as used by aede, aerb and aerpass?
1122 //
1123 assert(src_data->diff_file_fp);
1124 s2 = str_format("%s,D", s1->str_text);
1125 project_become(pp);
1126 ok = change_fingerprint_same(src_data->diff_file_fp, s2, 0);
1127 project_become_undo(pp);
1128 if (!ok)
1129 {
1130 sub_context_ty *scp;
1131
1132 scp = sub_context_new();
1133 sub_var_set_format
1134 (
1135 scp,
1136 "File_Name",
1137 "%s,D",
1138 src_data->file_name->str_text
1139 );
1140 change_error(cp, scp, i18n("$filename altered"));
1141 sub_context_delete(scp);
1142 errs++;
1143 }
1144 str_free(s2);
1145 assert(src_data->diff_file_fp->youngest > 0);
1146 assert(src_data->diff_file_fp->oldest > 0);
1147 }
1148 str_free(s1);
1149 }
1150 if (errs)
1151 quit(1);
1152
1153 //
1154 // add to the change history
1155 // (The time stamp is used later for the file mod times.)
1156 //
1157 os_throttle();
1158 history_data = change_history_new(cp, up);
1159 history_data->what = cstate_history_what_integrate_begin;
1160 history_data->why = reason;
1161
1162 //
1163 // Make sure the integration directory is not already there
1164 // (a user make have killed a previous aeib). Register a rmdir
1165 // (to be done in the background) if an undo is needed.
1166 //
1167 project_become(pp);
1168 if (os_exists(id))
1169 os_rmdir_tree(id);
1170 undo_rmdir_bg(id);
1171
1172 //
1173 // create the integration directory
1174 // copy everything from baseline to integration directory
1175 // except things from the change
1176 // and change owner to the project's owner.
1177 //
1178 if (pconf_data->link_integration_directory)
1179 {
1180 change_verbose(cp, 0, i18n("link baseline to integration directory"));
1181 dir_walk
1182 (
1183 bl,
1184 (minimum ? link_tree_callback_minimum : link_tree_callback),
1185 cp
1186 );
1187 }
1188 else
1189 {
1190 change_verbose(cp, 0, i18n("copy baseline to integration directory"));
1191 dir_walk
1192 (
1193 bl,
1194 (minimum ? copy_tree_callback_minimum : copy_tree_callback),
1195 cp
1196 );
1197 }
1198
1199 //
1200 // apply the changes to the integration directory
1201 //
1202 change_verbose(cp, 0, i18n("apply change to integration directory"));
1203 for (j = 0;; ++j)
1204 {
1205 fstate_src_ty *src_data;
1206 string_ty *s1;
1207 string_ty *s2;
1208
1209 src_data = change_file_nth(cp, j, view_path_first);
1210 if (!src_data)
1211 break;
1212 s1 = os_path_join(dd, src_data->file_name);
1213 s2 = os_path_join(id, src_data->file_name);
1214 if (os_exists(s2))
1215 {
1216 //
1217 // this is defensive,
1218 // and should never need to be executed
1219 //
1220 os_unlink(s2);
1221 }
1222 switch (src_data->action)
1223 {
1224 case file_action_insulate:
1225 //
1226 // This should never happen: aede will fail if
1227 // there are any insulation files.
1228 //
1229 assert(0);
1230 break;
1231
1232 case file_action_remove:
1233 case file_action_transparent:
1234 break;
1235
1236 case file_action_modify:
1237 case file_action_create:
1238 switch (src_data->usage)
1239 {
1240 case file_usage_build:
1241 break;
1242
1243 case file_usage_source:
1244 case file_usage_config:
1245 case file_usage_test:
1246 case file_usage_manual_test:
1247 #ifndef DEBUG
1248 default:
1249 #endif
1250 //
1251 // New files do not exist in the baseline,
1252 // and old files may not be copied under -MINimum,
1253 // so we may need to create directories.
1254 //
1255 os_mkdir_between(id, src_data->file_name, 02755);
1256 copy_whole_file(s1, s2, 0);
1257
1258 //
1259 // Set the mode of the file.
1260 //
1261 mode = 0444;
1262 if (os_executable(s1))
1263 mode |= 0111;
1264 os_chmod(s2, mode & ~cp->umask_get());
1265
1266 //
1267 // Make all of the change's files have the same
1268 // mod time, so that when aeipass flattens the
1269 // mod times, all of them fall into a single
1270 // second, minimizing the chance that mod times
1271 // will extend into the future after aeipass.
1272 //
1273 // This also helps cooperating DMTs flatten the
1274 // targets' mod times into a smaller range,
1275 // which also helps aeipass.
1276 //
1277 os_mtime_set(s2, history_data->when);
1278 break;
1279 }
1280 break;
1281 }
1282 str_free(s1);
1283 str_free(s2);
1284
1285 //
1286 // clear the file's test times
1287 //
1288 if (src_data->architecture_times)
1289 {
1290 fstate_src_architecture_times_list_type.free
1291 (
1292 src_data->architecture_times
1293 );
1294 src_data->architecture_times = 0;
1295 }
1296
1297 //
1298 // clear the file's integrate difference time
1299 //
1300 if (src_data->idiff_file_fp)
1301 {
1302 fingerprint_type.free(src_data->idiff_file_fp);
1303 src_data->idiff_file_fp = 0;
1304 }
1305 }
1306 project_become_undo(pp);
1307
1308 //
1309 // add the change to the user's list
1310 //
1311 trace_long(change_number);
1312 up->own_add(pp, change_number);
1313 cstate_data->state = cstate_state_being_integrated;
1314
1315 //
1316 // clear the change build times,
1317 // and test times
1318 //
1319 change_build_times_clear(cp);
1320
1321 //
1322 // write the data out
1323 // and release the locks
1324 //
1325 pp->pstate_write();
1326 cp->cstate_write();
1327 up->ustate_write();
1328 commit();
1329 lock_release();
1330
1331 //
1332 // run the integrate begin command
1333 //
1334 pup = project_user(pp);
1335 log_open(change_logfile_get(cp), pup, log_style);
1336 change_run_integrate_begin_command(cp);
1337
1338 //
1339 // Update the RSS feed file if necessary.
1340 //
1341 rss_add_item_by_change(pp, cp);
1342
1343 //
1344 // verbose success message
1345 //
1346 change_verbose(cp, 0, i18n("integrate begin complete"));
1347 change_free(cp);
1348 project_free(pp);
1349 trace(("}\n"));
1350 }
1351
1352
1353 void
integrate_begin()1354 integrate_begin()
1355 {
1356 static arglex_dispatch_ty dispatch[] =
1357 {
1358 { arglex_token_help, integrate_begin_help, 0 },
1359 { arglex_token_list, integrate_begin_list, 0 },
1360 };
1361
1362 trace(("integrate_begin()\n{\n"));
1363 arglex_dispatch(dispatch, SIZEOF(dispatch), integrate_begin_main);
1364 trace(("}\n"));
1365 }
1366
1367
1368 /* vim: set ts=8 sw=4 et : */
1369