1 //
2 // aegis - project change supervisor
3 // Copyright (C) 2001-2009, 2011, 2012 Peter Miller
4 // Copyright (C) 2008, 2009 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/unistd.h>
25 
26 #include <common/nstring.h>
27 #include <common/progname.h>
28 #include <common/str_list.h>
29 #include <common/trace.h>
30 #include <libaegis/arglex/change.h>
31 #include <libaegis/arglex/project.h>
32 #include <libaegis/cattr.fmtgen.h>
33 #include <libaegis/change/attributes.h>
34 #include <libaegis/change/file.h>
35 #include <libaegis/change.h>
36 #include <libaegis/change/lock_sync.h>
37 #include <libaegis/help.h>
38 #include <libaegis/input/base64.h>
39 #include <libaegis/input/bunzip2.h>
40 #include <libaegis/input/gunzip.h>
41 #include <libaegis/input/string.h>
42 #include <libaegis/meta_parse.h>
43 #include <libaegis/os.h>
44 #include <libaegis/output/file.h>
45 #include <libaegis/patch.h>
46 #include <libaegis/pconf.fmtgen.h>
47 #include <libaegis/project/file.h>
48 #include <libaegis/project/file/trojan.h>
49 #include <libaegis/project.h>
50 #include <libaegis/project/history.h>
51 #include <libaegis/sub.h>
52 #include <libaegis/undo.h>
53 #include <libaegis/user.h>
54 #include <libaegis/zero.h>
55 
56 #include <aepatch/arglex3.h>
57 #include <aepatch/receive.h>
58 #include <aepatch/slurp.h>
59 
60 
61 static void
usage(void)62 usage(void)
63 {
64     const char      *progname;
65 
66     progname = progname_get();
67     fprintf(stderr, "Usage: %s --receive [ <option>... ]\n", progname);
68     fprintf(stderr, "       %s --help\n", progname);
69     exit(1);
70 }
71 
72 
73 static string_ty *
is_a_path_prefix(string_ty * haystack,string_ty * needle)74 is_a_path_prefix(string_ty *haystack, string_ty *needle)
75 {
76     if
77     (
78         haystack->str_length > needle->str_length
79     &&
80         haystack->str_text[needle->str_length] == '/'
81     &&
82         0 == memcmp(haystack->str_text, needle->str_text, needle->str_length)
83     )
84     {
85         return
86             str_n_from_c
87             (
88                 haystack->str_text + needle->str_length + 1,
89                 haystack->str_length - needle->str_length - 1
90             );
91     }
92     return 0;
93 }
94 
95 
96 static string_ty *path_prefix_add;
97 static string_list_ty path_prefix_remove;
98 
99 
100 static void
mangle_file_names(patch_list_ty * plp,project * pp)101 mangle_file_names(patch_list_ty *plp, project *pp)
102 {
103     struct cmp_t
104     {
105         int             npaths;
106         int             idx;
107     };
108 
109     size_t          j;
110     cmp_t           best;
111     patch_ty        *p;
112     string_ty       *s;
113     int             npaths;
114     size_t          idx;
115     char            *cp;
116     string_ty       *dev_null;
117 
118     //
119     // First we chew over the filenames according to the path prefix
120     // options on the command line.
121     //
122     // First remove any path prefixes we have been asked to remove.
123     // Second add a path prefix if we have been asked to.
124     //
125     trace(("mangle_file_names()\n{\n"));
126     for (j = 0; j < plp->length; ++j)
127     {
128         p = plp->item[j];
129         for (idx = 0; idx < p->name.nstrings; ++idx)
130         {
131             size_t          k;
132 
133             for (k = 0; k < path_prefix_remove.nstrings; ++k)
134             {
135                 s =
136                     is_a_path_prefix
137                     (
138                         p->name.string[idx],
139                         path_prefix_remove.string[k]
140                     );
141                 if (s)
142                 {
143                     trace(("\"%s\" -> \"%s\"\n", p->name.string[idx]->str_text,
144                         s->str_text));
145                     str_free(p->name.string[idx]);
146                     p->name.string[idx] = s;
147                 }
148             }
149             if (path_prefix_add)
150             {
151                 s = os_path_cat(path_prefix_add, p->name.string[idx]);
152                 trace(("\"%s\" -> \"%s\"\n", p->name.string[idx]->str_text,
153                     s->str_text));
154                 str_free(p->name.string[idx]);
155                 p->name.string[idx] = s;
156             }
157         }
158     }
159 
160     //
161     // Look for the name with the fewest removed leading path
162     // compenents that produces a file name which exists in the
163     // project.
164     //
165     dev_null = str_from_c("/dev/null");
166     best.npaths = 32767;
167     best.idx = 32767;
168     for (j = 0; j < plp->length; ++j)
169     {
170         p = plp->item[j];
171         for (idx = 0; idx < p->name.nstrings; ++idx)
172         {
173             npaths = 0;
174             if (str_equal(dev_null, p->name.string[idx]))
175                 continue;
176             cp = p->name.string[idx]->str_text;
177             for (;;)
178             {
179                 s = str_from_c(cp);
180                 if (pp->file_find(s, view_path_extreme))
181                 {
182                     if
183                     (
184                         npaths < best.npaths
185                     ||
186                         (npaths == best.npaths && (int)idx < best.idx)
187                     )
188                     {
189                         best.npaths = npaths;
190                         best.idx = idx;
191                         break;
192                     }
193                 }
194                 cp = strchr(cp, '/');
195                 if (!cp)
196                     break;
197                 ++cp;
198                 if (!*cp)
199                     break;
200                 ++npaths;
201             }
202         }
203     }
204     str_free(dev_null);
205     if (best.npaths == 32767)
206     {
207         best.npaths = 0;
208         best.idx = 0;
209     }
210     trace(("best.npaths = %d\n", best.npaths));
211     trace(("best.idx = %d\n", best.idx));
212 
213     //
214     // Now adjust the file names, using the path information.
215     //
216     for (j = 0; j < plp->length; ++j)
217     {
218         //
219         // Rip the right number of path elements from the
220         // "best" name.  Note that we have to cope with the
221         // number of names in the patch being inconsistent.
222         //
223         trace(("%zd of %zd\n", j, plp->length));
224         p = plp->item[j];
225         trace(("nstrings = %d\n", (int)p->name.nstrings));
226         assert(p->name.nstrings);
227         if (p->name.nstrings == 0)
228             continue;
229         s = p->name.string[0];
230         if (best.idx < (int)p->name.nstrings)
231             s = p->name.string[best.idx];
232         trace(("\"%s\"\n", s->str_text));
233         cp = s->str_text;
234         for (npaths = best.npaths; npaths > 0; --npaths)
235         {
236             char *cp2 = strchr(cp, '/');
237             if (!cp2)
238                 break;
239             cp = cp2 + 1;
240         }
241         trace(("%s -> %s\n", p->name.string[0]->str_text, cp));
242 
243         //
244         // Insert the reconstructed name into the front of the
245         // list of names.
246         //
247         s = str_from_c(cp);
248         p->name.push_front(s);
249         str_free(s);
250     }
251     trace(("}\n"));
252 }
253 
254 
255 static long
number_of_files(string_ty * project_name,long change_number)256 number_of_files(string_ty *project_name, long change_number)
257 {
258     project      *pp;
259     change::pointer cp;
260     long            result;
261 
262     pp = project_alloc(project_name);
263     pp->bind_existing();
264     cp = change_alloc(pp, change_number);
265     change_bind_existing(cp);
266     result = change_file_count(cp);
267     change_free(cp);
268     project_free(pp);
269     return result;
270 }
271 
272 
273 static cstate_ty *
extract_meta_data(string_ty ** spp)274 extract_meta_data(string_ty **spp)
275 {
276     cstate_ty       *change_set;
277     const char      *begin;
278     const char      *end;
279     string_ty       *s;
280 
281     //
282     // See if the necessary text markers are present.
283     //
284     begin = strstr((*spp)->str_text, "Aegis-Change-Set-Begin");
285     if (!begin)
286         return 0;
287     while (*begin)
288     {
289         ++begin;
290         if (begin[-1] == '\n')
291             break;
292     }
293     end = strstr(begin, "Aegis-Change-Set-End");
294     if (!end)
295         return 0;
296     while (end > begin)
297     {
298         --end;
299         if (*end == '\n')
300             break;
301     }
302 
303     //
304     // Build an input source out of the marked text.
305     //
306     input ip = new input_string(nstring(begin, end - begin));
307 
308     //
309     // Crop the description to emit the meta-data
310     // (and everything following it).
311     //
312     end = begin;
313     while (end > (*spp)->str_text && end[-1] != '\n')
314         --end;
315     s = str_n_from_c((*spp)->str_text, end - (*spp)->str_text);
316     str_free(*spp);
317     *spp = s;
318 
319     //
320     // Decipher the meta data.
321     //
322     ip = new input_base64(ip);
323     ip = input_gunzip_open(ip);
324     ip = input_bunzip2_open(ip);
325     change_set = (cstate_ty *)parse_input(ip, &cstate_type);
326     ip.close();
327     return change_set;
328 }
329 
330 
331 static cstate_src_ty *
cstate_src_find(cstate_ty * cstate_data,string_ty * file_name)332 cstate_src_find(cstate_ty *cstate_data, string_ty *file_name)
333 {
334     size_t          j;
335 
336     if (!cstate_data)
337         return 0;
338     if (!cstate_data->src)
339         return 0;
340     for (j = 0; j < cstate_data->src->length; ++j)
341     {
342         cstate_src_ty   *src;
343 
344         src = cstate_data->src->list[j];
345         if (src && src->file_name && str_equal(src->file_name, file_name))
346             return src;
347     }
348     return 0;
349 }
350 
351 
352 void
receive(void)353 receive(void)
354 {
355     string_ty       *ifn;
356     string_ty       *s;
357     patch_list_ty   *plp;
358     size_t          j;
359     string_ty       *project_name;
360     long            change_number;
361     project      *pp;
362     change::pointer cp;
363     string_ty       *attribute_file_name;
364     cattr_ty        *cattr_data;
365     cattr_ty        *dflt;
366     string_ty       *dot;
367     string_ty       *devdir;
368     pconf_ty        *pconf_data;
369     string_ty       *dd;
370     const char      *delta;
371     int             config_seen;
372     int             trojan;
373     cstate_ty       *change_set;
374 
375     trace(("receive()\n{\n"));
376     project_name = 0;
377     change_number = 0;
378     ifn = 0;
379     devdir = 0;
380     delta = 0;
381     trojan = -1;
382     nstring output_filename;
383     arglex();
384     while (arglex_token != arglex_token_eoln)
385     {
386         switch (arglex_token)
387         {
388         default:
389             generic_argument(usage);
390             continue;
391 
392         case arglex_token_change:
393             arglex();
394             arglex_parse_change(&project_name, &change_number, usage);
395             continue;
396 
397         case arglex_token_project:
398             arglex();
399             arglex_parse_project(&project_name, usage);
400             continue;
401 
402         case arglex_token_file:
403             if (ifn)
404                 duplicate_option(usage);
405             switch (arglex())
406             {
407             default:
408                 option_needs_file(arglex_token_file, usage);
409                 // NOTREACHED
410 
411             case arglex_token_stdio:
412                 ifn = str_from_c("");
413                 break;
414 
415             case arglex_token_string:
416                 ifn = str_from_c(arglex_value.alv_string);
417                 break;
418             }
419             break;
420 
421         case arglex_token_directory:
422             if (devdir)
423             {
424                 duplicate_option(usage);
425                 // NOTREACHED
426             }
427             if (arglex() != arglex_token_string)
428             {
429                 option_needs_dir(arglex_token_directory, usage);
430                 // NOTREACHED
431             }
432             devdir = str_format(" --directory %s", arglex_value.alv_string);
433             break;
434 
435         case arglex_token_trojan:
436             if (trojan > 0)
437                 duplicate_option(usage);
438             if (trojan >= 0)
439             {
440                 too_many_trojans:
441                 mutually_exclusive_options
442                 (
443                     arglex_token_trojan,
444                     arglex_token_trojan_not,
445                     usage
446                 );
447             }
448             trojan = 1;
449             break;
450 
451         case arglex_token_trojan_not:
452             if (trojan == 0)
453                 duplicate_option(usage);
454             if (trojan >= 0)
455                 goto too_many_trojans;
456             trojan = 0;
457             break;
458 
459         case arglex_token_delta:
460             if (delta)
461                 duplicate_option(usage);
462             switch (arglex())
463             {
464             default:
465                 option_needs_number(arglex_token_delta, usage);
466                 // NOTREACHED
467 
468             case arglex_token_number:
469             case arglex_token_string:
470                 delta = arglex_value.alv_string;
471                 break;
472             }
473             break;
474 
475         case arglex_token_path_prefix_add:
476             if (path_prefix_add)
477                 duplicate_option(usage);
478             if (arglex() != arglex_token_string)
479                 option_needs_file(arglex_token_path_prefix_add, usage);
480             path_prefix_add = str_from_c(arglex_value.alv_string);
481             break;
482 
483         case arglex_token_path_prefix_remove:
484             if (arglex() != arglex_token_string)
485                 option_needs_file(arglex_token_path_prefix_remove, usage);
486             s = str_from_c(arglex_value.alv_string);
487             path_prefix_remove.push_back_unique(s);
488             str_free(s);
489             break;
490 
491         case arglex_token_output:
492             if (!output_filename.empty())
493                 duplicate_option(usage);
494             if (arglex() != arglex_token_string)
495             {
496                 option_needs_file(arglex_token_output, usage);
497                 // NOTREACHED
498             }
499             output_filename = nstring(arglex_value.alv_string);
500             break;
501         }
502         arglex();
503     }
504 
505     if (change_number && !output_filename.empty())
506     {
507         mutually_exclusive_options
508         (
509             arglex_token_change,
510             arglex_token_output,
511             usage
512         );
513     }
514 
515     //
516     // read the input
517     //
518     plp = patch_slurp(ifn);
519     assert(plp);
520 
521     if (!project_name)
522         project_name = plp->project_name;
523     if (!project_name)
524     {
525         nstring n = user_ty::create()->default_project();
526         project_name = str_copy(n.get_ref());
527     }
528 
529     //
530     // locate project data
531     //      (Even of we don't use it, this confirms it is a valid
532     //      project name.)
533     //
534     pp = project_alloc(project_name);
535     pp->bind_existing();
536 
537     //
538     // See if the initial prelude contains the project meta-data.
539     //
540     change_set = extract_meta_data(&plp->description);
541 
542     //
543     // Search the names in the patch, trying to figure out how much
544     // path prefix to throw away.  When this is done, name.string[0]
545     // is the name we will use for the files.
546     //
547     bool build_files_are_ok = true;
548     if (!change_set)
549     {
550         mangle_file_names(plp, pp);
551         build_files_are_ok = true;
552     }
553 
554     //
555     // default the change number
556     //
557     // Note that the change number in the patch is advisory only, if we
558     // can't get it, just use the next available.
559     //
560     if (!change_number)
561     {
562         if
563         (
564             plp->change_number
565         &&
566             !project_change_number_in_use(pp, plp->change_number)
567         )
568             change_number = plp->change_number;
569         else
570             change_number = project_next_change_number(pp, 1);
571     }
572 
573     //
574     // If the user asked for it, write the change number in the output
575     // file.
576     //
577     if (!output_filename.empty())
578     {
579         os_become_orig();
580         output::pointer ofp(output_file::open(output_filename));
581         os_become_undo();
582 
583         ofp->fprintf("%ld\n", change_number);
584     }
585 
586     //
587     // construct change attributes from the patch
588     //
589     os_become_orig();
590     attribute_file_name = os_edit_filename(0);
591     undo_unlink_errok(attribute_file_name);
592     cattr_data = (cattr_ty *)cattr_type.alloc();
593     if (change_set && change_set->brief_description)
594         cattr_data->brief_description = str_copy(change_set->brief_description);
595     else if (plp->brief_description)
596         cattr_data->brief_description = str_copy(plp->brief_description);
597     else
598         cattr_data->brief_description = str_from_c("none");
599     if (change_set && change_set->description)
600             cattr_data->description = str_copy(change_set->description);
601     else if (plp->description)
602         cattr_data->description = str_copy(plp->description);
603     else
604         cattr_data->description = str_from_c("none");
605     if (change_set && (change_set->mask & cstate_cause_mask))
606         cattr_data->cause = change_set->cause;
607     else
608         cattr_data->cause = change_cause_external_bug;
609     if (change_set && change_set->attribute)
610         cattr_data->attribute = attributes_list_copy(change_set->attribute);
611 
612     dflt = (cattr_ty *)cattr_type.alloc();
613     dflt->cause = cattr_data->cause;
614     os_become_undo();
615     pconf_data = project_pconf_get(pp);
616     change_attributes_default(dflt, pp, pconf_data);
617     os_become_orig();
618 
619     if (change_set && (change_set->mask & cstate_test_exempt_mask))
620         cattr_data->test_exempt = change_set->test_exempt;
621     else
622         cattr_data->test_exempt = dflt->test_exempt;
623     if (change_set && (change_set->mask & cstate_test_baseline_exempt_mask))
624         cattr_data->test_baseline_exempt = change_set->test_baseline_exempt;
625     else
626         cattr_data->test_baseline_exempt = dflt->test_baseline_exempt;
627     if (change_set && (change_set->mask & cstate_regression_test_exempt_mask))
628         cattr_data->regression_test_exempt = change_set->regression_test_exempt;
629     else
630         cattr_data->regression_test_exempt = dflt->regression_test_exempt;
631     cattr_type.free(dflt);
632     cattr_write_file(attribute_file_name, cattr_data, 0);
633     cattr_type.free(cattr_data);
634     project_free(pp);
635     pp = 0;
636 
637     nstring reason;
638     if (plp->comment)
639     {
640         reason =
641             (
642                 " --reason="
643             +
644                 ("Downloaded from " + nstring(plp->comment)).quote_shell()
645             );
646     }
647 
648     //
649     // create the new change
650     //
651     nstring trace_options(trace_args());
652     dot = os_curdir();
653     s =
654         str_format
655         (
656             "aegis --new-change %ld --project=%s --file=%s --verbose%s%s",
657             change_number,
658             project_name->str_text,
659             attribute_file_name->str_text,
660             reason.c_str(),
661             trace_options.c_str()
662         );
663     os_execute(s, OS_EXEC_FLAG_INPUT, dot);
664     str_free(s);
665     os_unlink_errok(attribute_file_name);
666     str_free(attribute_file_name);
667 
668     //
669     // Begin development of the new change.
670     //
671     s =
672         str_format
673         (
674             "aegis --develop-begin %ld --project %s --verbose%s%s",
675             change_number,
676             project_name->str_text,
677             (devdir ? devdir->str_text : ""),
678             trace_options.c_str()
679         );
680     os_execute(s, OS_EXEC_FLAG_INPUT, dot);
681     str_free(s);
682     os_become_undo();
683 
684     //
685     // Change to the development directory, so that we can use
686     // relative filenames.  It makes things easier to read.
687     //
688     pp = project_alloc(project_name);
689     pp->bind_existing();
690     cp = change_alloc(pp, change_number);
691     change_bind_existing(cp);
692     dd = change_development_directory_get(cp, 0);
693     dd = str_copy(dd); // will vanish when change_free();
694 
695     os_chdir(dd);
696 
697     //
698     // Adjust the file actions to reflect the current state of
699     // the project.
700     //
701     bool need_to_test = false;
702     bool could_have_a_trojan = false;
703     for (j = 0; j < plp->length; ++j)
704     {
705         patch_ty        *p;
706         fstate_src_ty   *p_src_data;
707         string_ty       *file_name;
708 
709         p = plp->item[j];
710         assert(p->name.nstrings >= 1);
711         file_name = p->name.string[0];
712         p_src_data = pp->file_find(file_name, view_path_extreme);
713         if (!p_src_data)
714         {
715             switch (p->action)
716             {
717             case file_action_remove:
718                 //
719                 // Removing a removed file would be an
720                 // error.  Get rid of it.
721                 //
722                 plp->item[j] = plp->item[plp->length - 1];
723                 plp->length--;
724                 --j;
725                 patch_delete(p);
726                 continue;
727 
728             case file_action_insulate:
729             case file_action_transparent:
730                 assert(0);
731 
732             case file_action_create:
733                 break;
734 
735             case file_action_modify:
736 #ifndef DEBUG
737             default:
738 #endif
739                 p->action = file_action_create;
740                 break;
741             }
742         }
743         else
744         {
745             switch (p->action)
746             {
747             case file_action_remove:
748                 break;
749 
750             case file_action_modify:
751                 break;
752 
753             case file_action_create:
754             case file_action_insulate:
755             case file_action_transparent:
756 #ifndef DEBUG
757             default:
758 #endif
759                 p->action = file_action_modify;
760                 break;
761             }
762         }
763         if (project_file_trojan_suspect(pp, file_name))
764             could_have_a_trojan = true;
765     }
766 
767     //
768     // add the modified files to the change
769     //
770     string_list_ty files_source;
771     string_list_ty files_test_auto;
772     string_list_ty files_test_manual;
773     for (j = 0; j < plp->length; ++j)
774     {
775         patch_ty        *p;
776         string_ty       *file_name;
777         cstate_src_ty   *csrc;
778 
779         //
780         // For now, we are only copying files.
781         //
782         p = plp->item[j];
783         assert(p->name.nstrings >= 1);
784         file_name = p->name.string[0];
785         csrc = cstate_src_find(change_set, file_name);
786         if (csrc)
787             p->usage = csrc->usage;
788         switch (p->action)
789         {
790         case file_action_modify:
791             break;
792 
793         case file_action_create:
794         case file_action_remove:
795         case file_action_insulate:
796         case file_action_transparent:
797 #ifndef DEBUG
798         default:
799 #endif
800             continue;
801         }
802         switch (p->usage)
803         {
804         case file_usage_build:
805             continue;
806 
807         case file_usage_source:
808         case file_usage_config:
809             break;
810 
811         case file_usage_test:
812         case file_usage_manual_test:
813             need_to_test = true;
814             break;
815         }
816 
817         //
818         // add it to the list
819         //
820         files_source.push_back_unique(file_name);
821     }
822     bool uncopy = false;
823     if (files_source.nstrings)
824     {
825         string_ty       *delopt;
826 
827         delopt = 0;
828         if (delta)
829         {
830             delopt = str_from_c(delta);
831             s = str_quote_shell(delopt);
832             str_free(delopt);
833             delopt = str_format(" --delta=%s", s->str_text);
834             str_free(s);
835         }
836         uncopy = true;
837         s =
838             str_format
839             (
840                 "aegis --copy-file --project=%s --change=%ld%s --verbose%s%s",
841                 project_name->str_text,
842                 change_number,
843                 trace_options.c_str(),
844                 (build_files_are_ok ? " -build" : ""),
845                 (delopt ? delopt->str_text : "")
846             );
847         if (delopt)
848             str_free(delopt);
849         os_xargs(s, &files_source, dd);
850         str_free(s);
851 
852         // change state invalid
853         change_lock_sync_forced(cp);
854     }
855 
856     //
857     // add the removed files to the change
858     //
859     files_source.clear();
860     for (j = 0; j < plp->length; ++j)
861     {
862         patch_ty        *p;
863 
864         //
865         // For now, we are only removing files.
866         //
867         p = plp->item[j];
868         assert(p->name.nstrings >= 1);
869         switch (p->action)
870         {
871         case file_action_remove:
872             break;
873 
874         case file_action_create:
875         case file_action_modify:
876         case file_action_insulate:
877         case file_action_transparent:
878 #ifndef DEBUG
879         default:
880 #endif
881             continue;
882         }
883 
884         //
885         // add it to the list
886         //
887         files_source.push_back_unique(p->name.string[0]);
888     }
889     if (files_source.nstrings)
890     {
891         s =
892             str_format
893             (
894                 "aegis --remove-file --project=%s --change=%ld%s --verbose",
895                 project_name->str_text,
896                 change_number,
897                 trace_options.c_str()
898             );
899         os_xargs(s, &files_source, dd);
900         str_free(s);
901 
902         // change state invalid
903         change_lock_sync_forced(cp);
904     }
905 
906     //
907     // add the new files to the change
908     //
909     files_source.clear();
910     string_list_ty files_config;
911     string_list_ty files_build;
912     for (j = 0; j < plp->length; ++j)
913     {
914         string_ty       *fn;
915         patch_ty        *p;
916 
917         //
918         // for now, we are only dealing with create
919         //
920         p = plp->item[j];
921         assert(p->name.nstrings >= 1);
922         switch (p->action)
923         {
924         case file_action_create:
925             break;
926 
927         case file_action_modify:
928         case file_action_remove:
929         case file_action_insulate:
930         case file_action_transparent:
931 #ifndef DEBUG
932         default:
933 #endif
934             continue;
935         }
936 
937         //
938         // add it to the list
939         //
940         fn = p->name.string[0];
941         switch (p->usage)
942         {
943         case file_usage_source:
944             if (cp->file_is_config(fn))
945                 files_config.push_back_unique(fn);
946             else
947                 files_source.push_back_unique(fn);
948             break;
949 
950         case file_usage_config:
951             files_config.push_back_unique(fn);
952             break;
953 
954         case file_usage_build:
955             files_build.push_back_unique(fn);
956             break;
957 
958         case file_usage_test:
959             files_test_auto.push_back_unique(fn);
960             need_to_test = true;
961             break;
962 
963         case file_usage_manual_test:
964             files_test_manual.push_back_unique(fn);
965             need_to_test = true;
966             break;
967         }
968     }
969 
970     if (files_test_auto.nstrings)
971     {
972         s =
973             str_format
974             (
975                 "aegis --new-test --automatic --project=%s --change=%ld%s "
976                     "--verbose --no-template",
977                 project_name->str_text,
978                 change_number,
979                 trace_options.c_str()
980             );
981         os_xargs(s, &files_test_auto, dd);
982         str_free(s);
983 
984         // change state invalid
985         change_lock_sync_forced(cp);
986     }
987     if (files_test_manual.nstrings)
988     {
989         s =
990             str_format
991             (
992                 "aegis --new-test --manual --project=%s --change=%ld%s "
993                     "--verbose --no-template",
994                 project_name->str_text,
995                 change_number,
996                 trace_options.c_str()
997             );
998         os_xargs(s, &files_test_manual, dd);
999         str_free(s);
1000 
1001         // change state invalid
1002         change_lock_sync_forced(cp);
1003     }
1004 
1005     //
1006     // NOTE: do this one last, in case it includes the first instance
1007     // of the project config file.
1008     //
1009     if (files_source.nstrings)
1010     {
1011         s =
1012             str_format
1013             (
1014                 "aegis --new-file --project=%s --change=%ld%s --verbose "
1015                     "--no-template --no-configured",
1016                 project_name->str_text,
1017                 change_number,
1018                 trace_options.c_str()
1019             );
1020         os_xargs(s, &files_source, dd);
1021         str_free(s);
1022 
1023         // change state invalid
1024         change_lock_sync_forced(cp);
1025     }
1026     if (files_build.nstrings)
1027     {
1028         s =
1029             str_format
1030             (
1031                 "aegis --new-file --build --project=%s --change=%ld%s "
1032                     "--verbose --no-template",
1033                 project_name->str_text,
1034                 change_number,
1035                 trace_options.c_str()
1036             );
1037         os_xargs(s, &files_build, dd);
1038         str_free(s);
1039 
1040         // change state invalid
1041         change_lock_sync_forced(cp);
1042     }
1043     if (files_config.nstrings)
1044     {
1045         s =
1046             str_format
1047             (
1048                 "aegis --new-file --config --project=%s --change=%ld%s "
1049                     "--verbose --no-template",
1050                 project_name->str_text,
1051                 change_number,
1052                 trace_options.c_str()
1053             );
1054         os_xargs(s, &files_config, dd);
1055         str_free(s);
1056 
1057         // change state invalid
1058         change_lock_sync_forced(cp);
1059     }
1060 
1061     //
1062     // now extract each file from the input
1063     //
1064     config_seen = 0;
1065     for (j = 0; j < plp->length; ++j)
1066     {
1067         patch_ty        *p;
1068         string_ty       *orig;
1069 
1070         // verbose progress message here?
1071         p = plp->item[j];
1072         switch (p->action)
1073         {
1074         case file_action_create:
1075         case file_action_modify:
1076             break;
1077 
1078         case file_action_remove:
1079         case file_action_insulate:
1080         case file_action_transparent:
1081             continue;
1082         }
1083         switch (p->usage)
1084         {
1085         case file_usage_build:
1086             continue;
1087 
1088         case file_usage_source:
1089             if (cp->file_is_config(p->name.string[0]))
1090             {
1091                 could_have_a_trojan = true;
1092                 config_seen = 1;
1093             }
1094             break;
1095 
1096         case file_usage_config:
1097             could_have_a_trojan = true;
1098             config_seen = 1;
1099             break;
1100 
1101         case file_usage_test:
1102         case file_usage_manual_test:
1103             could_have_a_trojan = true;
1104             break;
1105         }
1106         assert(p->name.nstrings >= 1);
1107         trace(("%s\n", p->name.string[0]->str_text));
1108 
1109         //
1110         // Recall that, somewhere above, we may have messed
1111         // with the `action' field, so we have to look at the
1112         // patch itself, and reconstruct whether it is creating
1113         // new content.
1114         //
1115         if
1116         (
1117             p->actions.length == 1
1118         &&
1119             p->actions.item[0]->before.length == 0
1120         &&
1121             p->actions.item[0]->before.start_line_number == 0
1122         )
1123             p->action = file_action_create;
1124 
1125         //
1126         // Apply the patch.
1127         //
1128         switch (p->action)
1129         {
1130         case file_action_create:
1131             os_become_orig();
1132             patch_apply(p, (string_ty *)0, p->name.string[0]);
1133             os_become_undo();
1134             break;
1135 
1136         case file_action_modify:
1137             //
1138             // This is the normal case: modify an existing file.
1139             //
1140             // The input file (to which the patch is applied) may
1141             // be found in the baseline.
1142             //
1143             orig = project_file_path(pp, p->name.string[0]);
1144             os_become_orig();
1145             patch_apply(p, orig, p->name.string[0]);
1146             os_become_undo();
1147             str_free(orig);
1148             break;
1149 
1150         case file_action_remove:
1151             break;
1152 
1153         case file_action_insulate:
1154         case file_action_transparent:
1155             assert(0);
1156             break;
1157         }
1158     }
1159     change_free(cp);
1160     cp = 0;
1161 
1162     if (change_set)
1163     {
1164         //
1165         // FIXME: update file attributes, using file attribute data from
1166         // the change_set
1167         //
1168     }
1169 
1170     //
1171     // Un-copy any files which did not change.
1172     //
1173     // The idea is, if there are no files left, there is nothing
1174     // for this change to do, so cancel it.
1175     //
1176     if (uncopy)
1177     {
1178         s =
1179             str_format
1180             (
1181                 "aegis --copy-file-undo --unchanged --change=%ld "
1182                     "--project=%s%s --verbose",
1183                 change_number,
1184                 project_name->str_text,
1185                 trace_options.c_str()
1186             );
1187         os_become_orig();
1188         os_execute(s, OS_EXEC_FLAG_INPUT, dd);
1189         os_become_undo();
1190         str_free(s);
1191 
1192         //
1193         // If there are no files left, we already have this change.
1194         //
1195         if (number_of_files(project_name, change_number) == 0)
1196         {
1197             //
1198             // get out of there
1199             //
1200             os_chdir(dot);
1201 
1202             //
1203             // stop developing the change
1204             //
1205             s =
1206                 str_format
1207                 (
1208                     "aegis --develop-begin-undo --change=%ld --project=%s%s "
1209                         "--verbose",
1210                     change_number,
1211                     project_name->str_text,
1212                     trace_options.c_str()
1213                 );
1214             os_become_orig();
1215             os_execute(s, OS_EXEC_FLAG_INPUT, dot);
1216             str_free(s);
1217 
1218             //
1219             // cancel the change
1220             //
1221             s =
1222                 str_format
1223                 (
1224                     "aegis --new-change-undo --change=%ld --project=%s%s "
1225                         "--verbose",
1226                     change_number,
1227                     project_name->str_text,
1228                     trace_options.c_str()
1229                 );
1230             os_execute(s, OS_EXEC_FLAG_INPUT, dot);
1231             os_become_undo();
1232             str_free(s);
1233 
1234             //
1235             // run away, run away!
1236             //
1237             error_intl(0, i18n("change already present"));
1238             return;
1239         }
1240     }
1241 
1242     //
1243     // If the change could have a trojan horse in it, stop here with
1244     // a warning.  The user needs to look at it and check.
1245     //
1246     if (trojan > 0)
1247         could_have_a_trojan = true;
1248     else if (trojan == 0)
1249     {
1250         error_intl(0, i18n("warning: potential trojan, proceeding anyway"));
1251         could_have_a_trojan = false;
1252         config_seen = 0;
1253     }
1254 
1255     //
1256     // If the change could have a trojan horse in the project config
1257     // file, stop here with a warning.  Don't even difference the
1258     // change, because the trojan could be embedded in the diff
1259     // command.  The user needs to look at it and check.
1260     //
1261     // FIX ME: what if the aecpu got rid of it?
1262     //
1263     if (config_seen)
1264     {
1265         error_intl
1266         (
1267             0,
1268          i18n("warning: potential trojan, review before completing development")
1269         );
1270         return;
1271     }
1272 
1273     //
1274     // now diff the change
1275     //
1276     s =
1277         str_format
1278         (
1279             "aegis --diff --no-merge --change=%ld --project=%s%s --verbose",
1280             change_number,
1281             project_name->str_text,
1282             trace_options.c_str()
1283         );
1284     os_become_orig();
1285     os_execute(s, OS_EXEC_FLAG_INPUT, dd);
1286     os_become_undo();
1287     str_free(s);
1288 
1289     //
1290     // Now that all the files have been unpacked,
1291     // set the change's UUID.
1292     //
1293     // It is vaguely possible you have already downloaded this change
1294     // before, so we don't complain (OS_EXEC_FLAG_ERROK) if the command
1295     // fails.
1296     //
1297     if (change_set && change_set->uuid)
1298     {
1299         string_ty       *quoted_uuid;
1300 
1301         quoted_uuid = str_quote_shell(change_set->uuid);
1302         s =
1303             str_format
1304             (
1305                 "aegis --change-attr --uuid %s -change=%ld --project=%s%s",
1306                 quoted_uuid->str_text,
1307                 change_number,
1308                 project_name->str_text,
1309                 trace_options.c_str()
1310             );
1311         str_free(quoted_uuid);
1312         os_become_orig();
1313         os_execute(s, OS_EXEC_FLAG_INPUT | OS_EXEC_FLAG_ERROK, dd);
1314         os_become_undo();
1315         str_free(s);
1316     }
1317 
1318     //
1319     // If the change could have a trojan horse in it, stop here with
1320     // a warning.  The user needs to look at it and check.
1321     //
1322     if (could_have_a_trojan)
1323     {
1324         error_intl
1325         (
1326             0,
1327          i18n("warning: potential trojan, review before completing development")
1328         );
1329         return;
1330     }
1331 
1332     //
1333     // Sleep for a second to make sure the derived files will have
1334     // mod-times strictly later than the source files, and that the aeb
1335     // timestamp will also be strictly later then the mod times for the
1336     // source files.
1337     //
1338     sleep(1);
1339 
1340     //
1341     // now build the change
1342     //
1343     s =
1344         str_format
1345         (
1346             "aegis --build --change=%ld --project=%s%s --verbose",
1347             change_number,
1348             project_name->str_text,
1349             trace_options.c_str()
1350         );
1351     os_become_orig();
1352     os_execute(s, OS_EXEC_FLAG_INPUT, dd);
1353     os_become_undo();
1354     str_free(s);
1355 
1356     //
1357     // Sleep for a second to make sure the aet timestamps will be
1358     // strictly later then the aeb timestamp.
1359     //
1360     sleep(1);
1361 
1362     //
1363     // re-read the change state data.
1364     //
1365     cp = change_alloc(pp, change_number);
1366     change_bind_existing(cp);
1367     cstate_ty *cstate_data = cp->cstate_get();
1368 
1369     //
1370     // now test the change
1371     //
1372     if (need_to_test && !cstate_data->test_exempt)
1373     {
1374         s =
1375             str_format
1376             (
1377                 "aegis --test --change=%ld --project=%s%s --verbose",
1378                 change_number,
1379                 project_name->str_text,
1380                 trace_options.c_str()
1381             );
1382         os_become_orig();
1383         os_execute(s, OS_EXEC_FLAG_INPUT, dd);
1384         os_become_undo();
1385         str_free(s);
1386     }
1387     if (need_to_test && !cstate_data->test_baseline_exempt)
1388     {
1389         s =
1390             str_format
1391             (
1392                 "aegis --test --baseline --change=%ld --project=%s%s --verbose",
1393                 change_number,
1394                 project_name->str_text,
1395                 trace_options.c_str()
1396             );
1397         os_become_orig();
1398         os_execute(s, OS_EXEC_FLAG_INPUT, dd);
1399         os_become_undo();
1400         str_free(s);
1401     }
1402 
1403     // always do a regession test?
1404     if (!cstate_data->regression_test_exempt)
1405     {
1406         s =
1407             str_format
1408             (
1409                 "aegis --test --regression --change=%ld --project=%s%s "
1410                 "--verbose",
1411                 change_number,
1412                 project_name->str_text,
1413                 trace_options.c_str()
1414             );
1415         os_become_orig();
1416         os_execute(s, OS_EXEC_FLAG_INPUT, dd);
1417         os_become_undo();
1418         str_free(s);
1419     }
1420 
1421     change_free(cp);
1422     cp = 0;
1423     project_free(pp);
1424     pp = 0;
1425 
1426     //
1427     // end development (if we got this far!)
1428     //
1429     s =
1430         str_format
1431         (
1432             "aegis --develop-end --change=%ld --project=%s%s --verbose",
1433             change_number,
1434             project_name->str_text,
1435             trace_options.c_str()
1436         );
1437     os_become_orig();
1438     os_execute(s, OS_EXEC_FLAG_INPUT, dd);
1439     os_become_undo();
1440     str_free(s);
1441 
1442     if (change_set)
1443         cstate_type.free(change_set);
1444 
1445     // verbose success message here?
1446     trace(("}\n"));
1447 }
1448 
1449 
1450 // vim: set ts=8 sw=4 et :
1451