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