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