// // aegis - project change supervisor // Copyright (C) 1999-2008 Peter Miller // Copyright (C) 2005-2008 Walter Franzini // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see // . // #include #include #include #include // for assert #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void move_xargs(const nstring &project_name, long change_number, const nstring_list &files, const nstring &dd, const nstring &trace_options) { if (0 != (files.size() % 2)) this_is_a_bug(); nstring command = nstring::format ( "aegis --move-file --project=%s --change=%ld%s --verbose", project_name.c_str(), magic_zero_decode(change_number), trace_options.c_str() ); // // aemv needs an even number of file names, the os_xargs function // run the commands with, at most, 50 args. Thus each aemv // invocation get an even number of file arguments. // os_xargs(command, files, dd); } static void change_uuid_set(const nstring &project_name, long change_number, const nstring &uuid, const nstring &trace_options, const nstring &dd) { nstring s = nstring::format ( "aegis --change-attr --uuid %s -change=%ld --project=%s%s", uuid.quote_shell().c_str(), magic_zero_decode(change_number), project_name.c_str(), trace_options.c_str() ); os_become_orig(); int err = os_execute_retcode(s, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); // // If we failed to set the change's UUID then we save its value // using the user defined attribute original-UUID. It's bad to // throw away UUIDs. // if (err) { nstring at = nstring::format("%s+=%s", ORIGINAL_UUID, uuid.c_str()); nstring s2 = nstring::format ( "aegis -change-attr %s -change=%ld -project=%s%s", at.quote_shell().c_str(), magic_zero_decode(change_number), project_name.c_str(), trace_options.c_str() ); os_become_orig(); os_execute(s2, OS_EXEC_FLAG_INPUT | OS_EXEC_FLAG_ERROK, dd); os_become_undo(); } } static long number_of_files(string_ty *project_name, long change_number) { project_ty *pp; change::pointer cp; long result; pp = project_alloc(project_name); pp->bind_existing(); cp = change_alloc(pp, change_number); change_bind_existing(cp); result = change_file_count(cp); change_free(cp); project_free(pp); return result; } static fstate_src_ty * project_file_find_by_meta2(project_ty *pp, cstate_src_ty *c_src_data, view_path_ty vp) { trace(("project_file_find_by_meta2(pp = %08lX, c_src = %08lX, vp = %s)\n" "{\n", (long)pp, (long)c_src_data, view_path_ename(vp))); trace (( "change: %s %s \"%s\" %s\n", file_usage_ename(c_src_data->usage), file_action_ename(c_src_data->action), c_src_data->file_name->str_text, (c_src_data->edit_number ? c_src_data->edit_number->str_text : "") )); #ifdef DEBUG if (c_src_data->move) trace((" move \"%s\"\n", c_src_data->move->str_text)); #endif // // Try to find the file by its UUID. // // For backwards compatibility reasons (UUIDs were introduced in // 4.17) there could be a rename chain (pointing at UUID-less // entries) to be taken into account. // if (c_src_data->uuid) { trace(("uuid = %s\n", c_src_data->uuid->str_text)); fstate_src_ty *p_src_data = pp->file_find_by_uuid(c_src_data->uuid, vp); while (p_src_data) { if (p_src_data->action != file_action_remove || !p_src_data->move) { trace (( "project: %s %s \"%s\" %s %s\n", file_usage_ename(p_src_data->usage), file_action_ename(p_src_data->action), p_src_data->file_name->str_text, (p_src_data->edit_origin ? p_src_data->edit_origin->revision->str_text : ""), (p_src_data->edit ? p_src_data->edit->revision->str_text : "") )); trace(("return %08lX;\n", (long)p_src_data)); trace(("}\n")); return p_src_data; } p_src_data = project_file_find(pp, p_src_data->move, vp); } } // // Look for the file by name, tracking renames as they happen. // string_ty *name = c_src_data->file_name; if (c_src_data->action == file_action_create && c_src_data->move) name = c_src_data->move; for (;;) { fstate_src_ty *p_src_data = project_file_find(pp, name, vp); if (!p_src_data) { trace(("return NULL;\n")); trace(("}\n")); return 0; } if (p_src_data->action != file_action_remove || !p_src_data->move) { trace (( "project: %s %s \"%s\" %s %s\n", file_usage_ename(p_src_data->usage), file_action_ename(p_src_data->action), p_src_data->file_name->str_text, (p_src_data->edit_origin ? p_src_data->edit_origin->revision->str_text : ""), (p_src_data->edit ? p_src_data->edit->revision->str_text : "") )); trace(("return %08lX;\n", (long)p_src_data)); trace(("}\n")); return p_src_data; } name = p_src_data->move; } } static cstate_src_ty * find_src(cstate_ty *cs, string_ty *filename) { assert(cs); assert(cs->src); for (size_t j = 0; j < cs->src->length; ++j) { cstate_src_ty *src = cs->src->list[j]; if (str_equal(src->file_name, filename)) return src; } return 0; } static void wrong_file(input &ifp, const nstring &expected) { nstring message = nstring::format ( "wrong file, expected \"%s\", " "maybe the sender needs to use the --compatibility=%s option", expected.c_str(), version_stamp() ); ifp->fatal_error(message.c_str()); } static void missing_file(input_cpio *ifp, const nstring &expected) { nstring message = nstring::format ( "missing file, expected \"%s\", " "maybe the sender needs to use the --compatibility=%s option", expected.c_str(), version_stamp() ); ifp->fatal_error(message.c_str()); } static void build_uuid_list(cstate_ty *csp, nstring_list &uuids) { if (csp->uuid) uuids.push_back(nstring(csp->uuid)); attributes_list_ty *alp = csp->attribute; if (!alp) return; for (size_t j = 0; j < alp->length; ++j) { attributes_ty *ap = alp->list[j]; assert(ap); if (!ap) continue; assert(ap->name); if (!ap->name) continue; assert(ap->value); if (!ap->value) continue; if (0 == strcasecmp(ORIGINAL_UUID, ap->name->str_text)) uuids.push_back(nstring(ap->value)); } } static bool nothing_has_changed(change::pointer cp, user_ty::pointer up) { // // First we do a quick scan to see if the can avoid comparing any // files at all. // for (size_t j = 0; ; ++j) { fstate_src_ty *src = change_file_nth(cp, j, view_path_first); if (!src) break; if (src->action != file_action_modify) return false; if (src->usage == file_usage_build) return false; } // // Compare each file in the change set with the same file in the // baseline. // for (size_t k = 0; ; ++k) { fstate_src_ty *src = change_file_nth(cp, k, view_path_first); if (!src) break; nstring path1(change_file_path(cp, src)); assert(!path1.empty()); nstring path2(project_file_path(cp->pp, src)); assert(!path2.empty()); up->become_begin(); bool different = files_are_different(path1, path2); up->become_end(); if (different) return false; } // // nothing has changed // return true; } void receive_main(void) { int use_patch; string_ty *project_name; long change_number; project_ty *pp; change::pointer cp; size_t j; cattr_ty *cattr_data; string_ty *attribute_file_name; move_list_ty files_moved; int could_have_a_trojan; int config_seen; int uncopy; int trojan; string_ty *devdir; int exec_mode; int non_exec_mode; int ignore_uuid; project_name = 0; change_number = 0; trojan = -1; nstring delta; devdir = 0; use_patch = -1; ignore_uuid = -1; nstring ifn; arglex(); while (arglex_token != arglex_token_eoln) { switch (arglex_token) { default: generic_argument(usage); continue; case arglex_token_change: arglex(); arglex_parse_change(&project_name, &change_number, usage); continue; case arglex_token_project: arglex(); arglex_parse_project(&project_name, usage); continue; case arglex_token_file: if (!ifn.empty()) duplicate_option(usage); switch (arglex()) { default: option_needs_file(arglex_token_file, usage); // NOTREACHED case arglex_token_string: ifn = arglex_value.alv_string; break; case arglex_token_stdio: ifn = "-"; break; } break; case arglex_token_trojan: if (trojan > 0) duplicate_option(usage); if (trojan >= 0) { too_many_trojans: mutually_exclusive_options ( arglex_token_trojan, arglex_token_trojan_not, usage ); } trojan = 1; break; case arglex_token_trojan_not: if (trojan == 0) duplicate_option(usage); if (trojan >= 0) goto too_many_trojans; trojan = 0; break; case arglex_token_delta: if (!delta.empty()) duplicate_option(usage); switch (arglex()) { default: option_needs_number(arglex_token_delta, usage); // NOTREACHED case arglex_token_number: delta = nstring::format(" --delta=%ld", arglex_value.alv_number); break; case arglex_token_string: { if (arglex_value.alv_string[0] == 0) option_needs_number(arglex_token_delta, usage); nstring arg = arglex_value.alv_string; arg = arg.quote_shell(); delta = nstring::format(" --delta=%s", arg.c_str()); } break; } break; case arglex_token_directory: if (devdir) { duplicate_option(usage); // NOTREACHED } if (arglex() != arglex_token_string) { option_needs_dir(arglex_token_directory, usage); // NOTREACHED } devdir = str_format(" --directory %s", arglex_value.alv_string); break; case arglex_token_patch: if (use_patch > 0) duplicate_option(usage); if (use_patch >= 0) { too_many_patchs: mutually_exclusive_options ( arglex_token_patch, arglex_token_patch_not, usage ); } use_patch = 1; break; case arglex_token_patch_not: if (use_patch == 0) duplicate_option(usage); if (use_patch >= 0) goto too_many_patchs; use_patch = 0; break; case arglex_token_ignore_uuid: if (ignore_uuid > 0) duplicate_option(usage); if (ignore_uuid >= 0) { too_many_ignore_uuid: mutually_exclusive_options ( arglex_token_ignore_uuid, arglex_token_ignore_uuid_not, usage ); } ignore_uuid = 1; break; case arglex_token_ignore_uuid_not: if (ignore_uuid == 0) duplicate_option(usage); if (ignore_uuid >= 0) goto too_many_ignore_uuid; ignore_uuid = 0; break; } arglex(); } // // Open the input file and verify the format. // input_cpio *cpio_p = aedist_open(ifn); assert(cpio_p); // // Calculate the --reason option to use with the // "aegis --new-change" command, if any. // nstring reason; bool remote = false; if (cpio_p->is_remote()) { reason = " --reason=" + ("Downloaded from " + cpio_p->name()).quote_shell(); remote = true; } // // read the project name from the archive, // and use it to default the project if not given // os_become_orig(); nstring archive_name; input ifp = cpio_p->child(archive_name); if (!ifp.is_open()) missing_file(cpio_p, "etc/project-name"); assert(!archive_name.empty()); if (archive_name != "etc/project-name") wrong_file(ifp, "etc/project-name"); { nstring s; if (!ifp->one_line(s) || s.empty()) ifp->fatal_error("short file"); if (!project_name) project_name = str_copy(s.get_ref()); } ifp.close(); os_become_undo(); // // locate project data // (Even of we don't use it, this confirms it is a valid // project name.) // pp = project_alloc(project_name); pp->bind_existing(); // // Read the change number form the archive, if it's there. Use that // number (a) if the user didn't specify one on the command line, // and (b) that number is available. // os_become_orig(); archive_name.clear(); ifp = cpio_p->child(archive_name); if (!ifp.is_open()) missing_file(cpio_p, "etc/change-set"); assert(archive_name); if (archive_name == "etc/change-number") { nstring s; if (!ifp->one_line(s) || s.empty()) ifp->fatal_error("short file"); long proposed_change_number = s.to_long(); ifp.close(); os_become_undo(); // // Make sure the change number is available. // if ( !change_number && proposed_change_number > 0 && !project_change_number_in_use(pp, proposed_change_number) ) change_number = proposed_change_number; // // Start the next file, so we are in the same state as when // there is no change number included. // archive_name.clear(); os_become_orig(); ifp = cpio_p->child(archive_name); if (!ifp.is_open()) missing_file(cpio_p, "etc/change-set"); } os_become_undo(); // // default the change number // if (!change_number) change_number = project_next_change_number(pp, 1); // // get the change details from the input // os_become_orig(); if (archive_name != "etc/change-set") wrong_file(ifp, "etc/change-set"); cstate_ty *change_set = (cstate_ty *)parse_input(ifp, &cstate_type); ifp.close(); os_become_undo(); nstring_list incoming_uuids; build_uuid_list(change_set, incoming_uuids); // // Make sure we like the change set at a macro level. // if ( !change_set->brief_description || !change_set->description || !change_set->src || !change_set->src->length ) cpio_p->fatal_error("bad change set"); for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src_data; src_data = change_set->src->list[j]; if ( !src_data->file_name || !src_data->file_name->str_length || !(src_data->mask & cstate_src_action_mask) || !(src_data->mask & cstate_src_usage_mask) ) cpio_p->fatal_error("bad change info"); } // // We refuse to receive a change we already have because that can // undo changes. If the local repository is the source of the // change set, then the patch will fail to apply and the full // source will be used, thus successive changes will be removed. // The -ignore-uuid option is here to handle UUID clash, if any. // nstring branch; nstring original_uuid; time_t original_ipass_when = 0; symtab local_inventory; bool include_branches = true; bool all_changes = false; bool ignore_original_uuid = true; change_functor_inventory_builder cf(include_branches, all_changes, ignore_original_uuid, pp, &local_inventory); project_inventory_walk(pp, cf); if (ignore_uuid <= 0) { if (change_set->uuid) { assert(universal_unique_identifier_valid(change_set->uuid)); change::pointer c = local_inventory.query(change_set->uuid); if (c) { assert(c->cstate_data->uuid); assert(universal_unique_identifier_valid(c->cstate_data->uuid)); assert(str_equal(change_set->uuid, c->cstate_data->uuid)); // // run away, run away! // error_intl(0, i18n("change already present")); return; } } // // We now add to the local inventory also the original-UUIDs. // ignore_original_uuid = false; change_functor_inventory_builder cf2(include_branches, all_changes, ignore_original_uuid, pp, &local_inventory); project_inventory_walk(pp, cf2); // // If the user has not specified a delta to copy file from, we // try to guess using the original-UUID attribute of the // change, if any. // if (delta.empty() && change_set->attribute) { // // Using the 'original-UUID' of the received change we look // for a completed change with a matching 'uuid'. // for (size_t i = 0; i < change_set->attribute->length; ++i) { attributes_ty *current = change_set->attribute->list[i]; if (!current || !current->name || !current->value) continue; if (0 != strcasecmp(current->name->str_text, ORIGINAL_UUID)) continue; if (!universal_unique_identifier_valid(current->value)) continue; change::pointer ancestor = local_inventory.query(current->value); if (!ancestor || !ancestor->is_completed()) continue; trace_string(current->value->str_text); // // We must keep track of the original-uuid and of the // timestamp of the change completion for later use in // the delta selection stage. // original_uuid = nstring(current->value); original_ipass_when = change_when_get ( ancestor, cstate_history_what_integrate_pass ); // // This works even if the ancestor change is // in the trunk. // assert(change_delta_number_get(ancestor) > 0); delta = nstring::format ( " --delta=%ld", change_delta_number_get(ancestor) ); trace_string(delta.c_str()); if (ancestor->pp) branch = nstring(project_version_short_get(ancestor->pp)); if (!branch.empty()) branch = " --branch=" + branch; else branch = " --trunk"; trace_nstring(branch); break; } } } // // We need to work out if we are a project administartor. This lets // us pass the testing atrributes through literally, rather than // modulo the defaults. // user_ty::pointer up = user_ty::create(); bool am_admin = project_administrator_query(pp, up->name()); // // construct change attributes from the change_set // // Be careful when copying across the testing exemptions, to // make sure we don't ask for an exemption we can't have. // os_become_orig(); attribute_file_name = os_edit_filename(0); undo_unlink_errok(attribute_file_name); os_become_undo(); cattr_data = (cattr_ty *)cattr_type.alloc(); cattr_data->brief_description = str_copy(change_set->brief_description); cattr_data->description = str_copy(change_set->description); cattr_data->cause = change_set->cause; if (am_admin) { // // Only project administrators can do this, otherwise we risk // having the "aegis --new-change" command fail, which isn't nice. // cattr_data->test_exempt = change_set->test_exempt; cattr_data->test_baseline_exempt = change_set->test_baseline_exempt; cattr_data->regression_test_exempt = change_set->regression_test_exempt; } else { cattr_ty *dflt = (cattr_ty *)cattr_type.alloc(); dflt->cause = change_set->cause; pconf_ty *pconf_data = project_pconf_get(pp); change_attributes_default(dflt, pp, pconf_data); cattr_data->test_exempt = (change_set->test_exempt && dflt->test_exempt); cattr_data->test_baseline_exempt = (change_set->test_baseline_exempt && dflt->test_baseline_exempt); cattr_data->regression_test_exempt = ( change_set->regression_test_exempt && dflt->regression_test_exempt ); cattr_type.free(dflt); } if (change_set->attribute) cattr_data->attribute = attributes_list_copy(change_set->attribute); if (remote) { if (!cattr_data->attribute) cattr_data->attribute = (attributes_list_ty *)attributes_list_type.alloc(); attributes_list_insert ( cattr_data->attribute, "foreign-copyright", "true" ); } os_become_orig(); cattr_write_file(attribute_file_name, cattr_data, 0); os_become_undo(); cattr_type.free(cattr_data); cattr_data = 0; project_free(pp); pp = 0; // // create the new change // nstring trace_options(trace_args()); os_become_orig(); nstring dot(os_curdir()); nstring new_change_command = nstring::format ( "aegis --new-change %ld --project=%s --file=%s --verbose%s%s", magic_zero_decode(change_number), project_name->str_text, attribute_file_name->str_text, reason.c_str(), trace_options.c_str() ); os_execute(new_change_command, OS_EXEC_FLAG_INPUT, dot); os_unlink_errok(attribute_file_name); // // Begin development of the new change. // nstring develop_begin_command = nstring::format ( "aegis --develop-begin %ld --project %s --verbose%s%s", magic_zero_decode(change_number), project_name->str_text, (devdir ? devdir->str_text : ""), trace_options.c_str() ); os_execute(develop_begin_command, OS_EXEC_FLAG_INPUT, dot); os_become_undo(); // // Change to the development directory, so that we can use // relative filenames. It makes things easier to read. // pp = project_alloc(project_name); pp->bind_existing(); cp = change_alloc(pp, change_number); change_bind_existing(cp); nstring dd(change_development_directory_get(cp, 0)); change_free(cp); cp = 0; os_chdir(dd); // // Adjust the file actions to ensure that renames are really // renames. Historical versions of Aegis were not as fussy as now, // and you could get half a rename by aermu or aenfu of one half. // trace(("check that renames are complete\n")); for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src1 = change_set->src->list[j]; if (!src1->move) continue; trace(("%s \"%s\", move=\"%s\"\n", file_action_ename(src1->action), src1->file_name->str_text, src1->move->str_text)); cstate_src_ty *src2 = find_src(change_set, src1->move); if (!src2) { // Only half a move. trace(("half a move\n")); str_free(src1->move); src1->move = 0; continue; } switch (src1->action) { case file_action_create: if (src2->action != file_action_remove) { // Something really weird happened. trace(("not a move\n")); assert(0); str_free(src1->move); src1->move = 0; } break; case file_action_remove: if (src2->action != file_action_create) { // // Something happened to the file after the rename. // This can occur when the change is a branch. // trace(("corresponds to %s \"%s\"\n", file_action_ename(src2->action), src2->file_name->str_text)); trace(("not a simple move\n")); // // If the target was later removed we short-circuit. // This is needed because some versions of aedist -send // erroneously represent (rename -> remove) by // (rename <--> rename). // This solution is wrong if a change_set can contain // a chain, but the sender supposedly collapses chains. // if (src2->action == file_action_remove) { str_free(src1->move); src1->move = 0; } } break; case file_action_modify: case file_action_insulate: case file_action_transparent: #ifdef DEBUG default: #endif // // None of these actions are supposed to have the move field // set. // trace(("not a move\n")); assert(0); str_free(src1->move); src1->move = 0; break; } } // // Adjust the file actions to reflect the current state of // the project. // trace(("adjust file actions\n")); bool need_to_test = false; could_have_a_trojan = 0; symtab old_name; for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src_data; fstate_src_ty *p_src_data; src_data = change_set->src->list[j]; assert(src_data); assert(src_data->file_name); trace(("%ld %s %s\n", (long) j, src_data->file_name->str_text, file_action_ename(src_data->action))); p_src_data = project_file_find_by_meta2(pp, src_data, view_path_extreme); switch (src_data->action) { case file_action_remove: // // Removing a removed file would be an // error, so take it out of the list completely. // trace(("remove\n")); if (!p_src_data) { trace(("no matching project file\n")); if (src_data->move) { // // does the target file exist? // trace(("which makes rename difficult\n")); p_src_data = project_file_find ( pp, src_data->move, view_path_extreme ); cstate_src_ty *src2 = find_src(change_set, src_data->move); assert(src2); // we already verified this if (p_src_data) { trace(("rewrite as modify\n")); src2->action = file_action_modify; } else { trace(("rewrite as create\n")); src2->action = file_action_create; } str_free(src2->move); src2->move = 0; // // We also want to ignore the remove half of the // rename, so fall through... // } // // It's tempting to fill the hole the removed element // leaves with the last list element and in doing so, // shortening the list. But that would mean the order // of the files in the change set differs from the order // of the files in the cpio archive, but this would // create problems later. We have to do it the long way // and shuffle everything down. // nuke_and_skip: trace(("nuke and skip\n")); for (size_t k = j + 1; k < change_set->src->length; ++k) change_set->src->list[k - 1] = change_set->src->list[k]; cstate_src_type.free(src_data); --j; change_set->src->length--; continue; } // // The view_path_extreme argument to project_find_file_by_ // meta2 was supposed to take care of making removed files // disappear. // assert(p_src_data->action != file_action_remove); // // Take care of removing and renaming files which don't have // the same name in the project as in the incoming change set. // if (!str_equal(src_data->file_name, p_src_data->file_name)) { if (src_data->move) { if (str_equal(src_data->move, p_src_data->file_name)) { // // The file has already been renamed, so there // is no need to remove it, but instead we turn // the target half into a modify action if it is // just a create action. // trace(("rename becomes modify\n")); cstate_src_ty *src2 = find_src(change_set, src_data->move); if (src2 && (src2->action == file_action_create)) { assert(src2->move); assert(str_equal(src2->move,src_data->file_name)); str_free(src2->move); src2->move = 0; src2->action = file_action_modify; } goto nuke_and_skip; } // // We are about to update the file name in the // incoming change set to match the file name in the // project. But first we must update the create // half of the rename as well. // trace(("fix rename To half\n")); cstate_src_ty *src2 = find_src(change_set, src_data->move); assert(src2); assert(src2->move); assert(str_equal(src2->move, src_data->file_name)); str_free(src2->move); src2->move = str_copy(p_src_data->file_name); } // // Update the file name in the incoming change set to // match the file name in the project. // Save the old file name, we will use it to look into // the archive. // trace(("file name changed\n")); old_name.assign ( nstring(p_src_data->file_name), nstring(src_data->file_name) ); str_free(src_data->file_name); src_data->file_name = str_copy(p_src_data->file_name); } break; case file_action_transparent: trace(("transparent\n")); assert(!src_data->move); if (!p_src_data) goto nuke_and_skip; // // Do we need to change the name of the file? // if (!str_equal(src_data->file_name, p_src_data->file_name)) { trace(("file name changed\n")); // // Save the old file name, we will use it to look into // the archive. // old_name.assign ( nstring(p_src_data->file_name), nstring(src_data->file_name) ); str_free(src_data->file_name); src_data->file_name = str_copy(p_src_data->file_name); } // FIXME: do we need to check that making it transparent is // even possible in this branch?!? break; case file_action_create: trace(("create\n")); if (src_data->move) { // // Is the file missing from the repository? // Has the rename already happened? // trace(("create half or rename\n")); if ( !p_src_data || str_equal(src_data->file_name, p_src_data->file_name) ) { // // Alter the file action. // if (p_src_data) { trace(("rename becomes modify\n")); src_data->action = file_action_modify; } else { trace(("rename becomes create\n")); src_data->action = file_action_create; } // // Nuke the remove half of the rename. // // We can't just leave it for the remove half to // clean up, because it may be after this point, // and the clues are gone. Plus, it would break // the assertion that all renames have both halves // present in the file list. // size_t m = 0; for (m = 0; m < change_set->src->length; ++m) { cstate_src_ty *src2 = change_set->src->list[m]; if (str_equal(src_data->move, src2->file_name)) { assert(src2->action == file_action_remove); cstate_src_type.free(src2); break; } } assert(m < change_set->src->length); for (size_t k = m + 1; k < change_set->src->length; ++k) change_set->src->list[k - 1] = change_set->src->list[k]; str_free(src_data->move); src_data->move = 0; // // We need to go back one step only if the remove // half was before the current file. // if (m < j) --j; change_set->src->length--; } else { // // The rename hasn't happened yet. // Do not mess with the file names. // trace(("rename is clean\n")); } } else { // // The file exists in the project. Alter the incoming // change set to modify the file. // if (p_src_data) { // // FIXME: we need to look for tests with the same // name but different UUIDs, and automagically give // them a new name, rather than simply ignoring the // UUID. // trace(("create becomes modify\n")); src_data->action = file_action_modify; // // If the name is different (this only happens when // the incoming file has a UUID) make it match the // project. // if (!str_equal(src_data->file_name, p_src_data->file_name)) { trace(("file name changed\n")); // // Save the old file name, we will use it to // look into the archive. // old_name.assign ( nstring(p_src_data->file_name), nstring(src_data->file_name) ); str_free(src_data->file_name); src_data->file_name = str_copy(p_src_data->file_name); } } } break; case file_action_insulate: assert(0); // fall through... case file_action_modify: trace(("modify\n")); assert(!src_data->move); if (!p_src_data) { // // The named file doesn't exist in the project, to // convert the action into a create instead. // trace(("modify becomes create\n")); src_data->action = file_action_create; } else { // // Make sure the action is a modify (it is supposed to // be impossible for an insulate action to get here, but // you never know). // src_data->action = file_action_modify; // // If the name is different (this only happens when // the incoming file has a UUID) make it match the // project. // if (!str_equal(src_data->file_name, p_src_data->file_name)) { trace(("file name changed\n")); // // Save the old file name, we will use it to look // into the archive. // old_name.assign ( nstring(p_src_data->file_name), nstring(src_data->file_name) ); str_free(src_data->file_name); src_data->file_name = str_copy(p_src_data->file_name); } } break; } // // Watch out for infection vectors. // trace(("check for trojans\n")); if (project_file_trojan_suspect(pp, src_data->file_name)) could_have_a_trojan = 1; } // // add the removed files to the change // trace(("look for removed files\n")); move_list_constructor(&files_moved); nstring_list files_source; nstring_list files_config; nstring_list files_build; nstring_list files_test_auto; nstring_list files_test_manual; for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src_data; fstate_src_ty *p_src_data; // // For now, we are only removing files. // src_data = change_set->src->list[j]; assert(src_data); assert(src_data->file_name); p_src_data = project_file_find_by_meta2(pp, src_data, view_path_extreme); switch (src_data->action) { case file_action_remove: break; case file_action_modify: assert(p_src_data); if (p_src_data && p_src_data->usage != src_data->usage) { // // When files change type, it is necessary to remove // them *and* then create them in the same change. // Make sure the create loop also creates this file. // switch (p_src_data->usage) { case file_usage_config: // // We are receiving an archive that contains a file // that our repository knows as file_usage_config, // but deleting the last config file is forbidden // and is wrong because we are receiving an old // style archive. Thus we skip the deletion. // continue; case file_usage_source: case file_usage_test: case file_usage_manual_test: case file_usage_build: #ifndef DEBUG default: #endif trace(("modify becomes create\n")); src_data->action = file_action_create; if (src_data->uuid) { // // Remove the UUID so that we don't try to set // it later (because we do this, later, for all // created files with UUIDs). // str_free(src_data->uuid); src_data->uuid = 0; } break; } break; } continue; case file_action_create: case file_action_insulate: case file_action_transparent: #ifndef DEBUG default: #endif continue; } // // add it to the list // if (src_data->move) { assert(src_data->action == file_action_remove); move_list_append_remove ( &files_moved, src_data->file_name, src_data->move ); } else files_source.push_back_unique(nstring(src_data->file_name)); } if (!files_source.empty()) { nstring s = nstring::format ( "aegis --remove-file --project=%s --change=%ld%s --verbose", project_name->str_text, magic_zero_decode(change_number), trace_options.c_str() ); os_xargs(s, files_source, dd); } // // add the modified files to the change // trace(("look for modified files\n")); files_source.clear(); symtab files_source_by_origin; for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src_data; // // For now, we are only copying files. // src_data = change_set->src->list[j]; assert(src_data->file_name); switch (src_data->action) { case file_action_modify: switch (src_data->usage) { case file_usage_build: break; case file_usage_test: case file_usage_manual_test: need_to_test = true; // fall through... case file_usage_source: case file_usage_config: // // We must initialize the origin with the information // from previous processing. // nstring origin = branch + delta; // // We have selected an origin delta using the // original-UUID attribute contained in the archive. // If we are receiving a branch that also contain the // entire_source we must check that the change set // that the UUID identify contains every single file. // if (!original_uuid.empty() && !branch.empty()) { change::pointer ancestor = local_inventory.query(original_uuid); assert(ancestor); fstate_src_ty *fsrc = change_file_find ( ancestor, src_data->file_name, view_path_first ); // // We reset the origin if the ancestor change does // not contain the file. // if (!fsrc) origin = ""; } if (src_data->attribute) { attributes_ty *edit_origin_UUID = attributes_list_find ( src_data->attribute, EDIT_ORIGIN_UUID ); if (edit_origin_UUID) { change::pointer ancestor = local_inventory.query(edit_origin_UUID->value); if (ancestor) { assert(change_delta_number_get(ancestor) > 0); time_t ancestor_ipass_when = change_when_get ( ancestor, cstate_history_what_integrate_pass ); // // We select the delta to merge with using // the most recently integrated change set. // if (ancestor_ipass_when > original_ipass_when) { // // We cannot reuse existing delta and // branch variables, which used to // contains data from previous processing, // originl-UUID or options related, // because this will cause wrong origin // selection. // nstring ancestor_delta = nstring::format ( " --delta=%ld", change_delta_number_get(ancestor) ); trace_string(ancestor_delta.c_str()); nstring ancestor_branch = nstring ( project_version_short_get(ancestor->pp) ); if (!ancestor_branch.empty()) ancestor_branch = " --branch=" + ancestor_branch; else ancestor_branch = " --trunk"; trace_nstring(ancestor_branch); origin = ancestor_branch + ancestor_delta; } } } } // // We create a hash table with origin as the key and // with the list of files to be copied as the value. // nstring_list *file_list = files_source_by_origin.query(origin); if (!file_list) { file_list = new nstring_list; files_source_by_origin.assign(origin, file_list); } file_list->push_back_unique(nstring(src_data->file_name)); break; } break; case file_action_create: case file_action_remove: case file_action_transparent: break; case file_action_insulate: #ifndef DEBUG default: #endif assert(0); break; } // // FIXME: we need to look for tests with the same name but // different UUIDs, and automagically give them a new name, // rather than simply ignoring the UUID. // } uncopy = 0; // // We copy modified files from the baseline grouping them using // the origin. // if (!files_source_by_origin.empty()) { uncopy = 1; nstring_list origin; files_source_by_origin.keys(origin); assert(!origin.empty()); for (size_t c = 0; c < origin.size(); ++c) { nstring_list *files_list = files_source_by_origin.query(origin[c]); assert(!files_list->empty()); nstring s = nstring::format ( "aegis --copy-file --project=%s --change=%ld%s " "--verbose%s", project_name->str_text, magic_zero_decode(change_number), trace_options.c_str(), origin[c].c_str() ); os_xargs(s, *files_list, dd); } } // // add the new files to the change // trace(("look for created files\n")); files_source.clear(); for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src_data; // // for now, we are only dealing with create // src_data = change_set->src->list[j]; assert(src_data->file_name); switch (src_data->action) { case file_action_create: break; case file_action_modify: case file_action_remove: case file_action_transparent: continue; case file_action_insulate: #ifndef DEBUG default: #endif assert(0); continue; } // // add it to the list // if (src_data->move) { assert(src_data->action == file_action_create); move_list_append_create ( &files_moved, src_data->move, src_data->file_name ); continue; } switch (src_data->usage) { case file_usage_source: files_source.push_back_unique(nstring(src_data->file_name)); break; case file_usage_config: files_config.push_back_unique(nstring(src_data->file_name)); break; case file_usage_build: files_build.push_back_unique(nstring(src_data->file_name)); break; case file_usage_test: files_test_auto.push_back_unique(nstring(src_data->file_name)); need_to_test = true; break; case file_usage_manual_test: files_test_manual.push_back_unique(nstring(src_data->file_name)); need_to_test = true; break; } } if (!files_build.empty()) { nstring s = nstring::format ( "aegis --new-file --build --project=%s --change=%ld%s " "--verbose --no-template", project_name->str_text, magic_zero_decode(change_number), trace_options.c_str() ); os_xargs(s, files_build, dd); } if (!files_test_auto.empty()) { nstring s = nstring::format ( "aegis --new-test --automatic --project=%s --change=%ld%s " "--verbose --no-template", project_name->str_text, magic_zero_decode(change_number), trace_options.c_str() ); os_xargs(s, files_test_auto, dd); } if (!files_test_manual.empty()) { nstring s = nstring::format ( "aegis --new-test --manual --project=%s --change=%ld%s " "--verbose --no-template", project_name->str_text, magic_zero_decode(change_number), trace_options.c_str() ); os_xargs(s, files_test_manual, dd); } if (!files_source.empty()) { nstring s = nstring(THE_CONFIG_FILE_OLD); // FIXME: need to use the "is a config file" function if (files_source.member(s)) { // // The project config file must be created in the last set // of files created, so move it to the end of the list. // files_source.remove(s); files_config.push_back(s); } nstring new_file_command = nstring::format ( "aegis --new-file --project=%s --change=%ld%s --verbose " "--no-template", project_name->str_text, magic_zero_decode(change_number), trace_options.c_str() ); os_xargs(new_file_command, files_source, dd); } // // NOTE: Create project configuration files one last, in case the // incoming change set includes the first instance of the project // configuration file. We don't want errors or inconsistencies in // the project configuration files preventing us from sucessfully // unpacking the change set. // if (!files_config.empty()) { nstring s = nstring::format ( "aegis --new-file --config --project=%s --change=%ld%s " "--verbose --no-template", project_name->str_text, magic_zero_decode(change_number), trace_options.c_str() ); os_xargs(s, files_config, dd); } // // Now cope with files which moved. // They get a command each. // nstring_list batch_moved; for (j = 0; j < files_moved.length; ++j) { move_ty *mp = files_moved.item + j; trace(("from=\"%s\"\n", mp->from ? mp->from->str_text : "")); trace(("remove=%d\n", mp->remove)); trace(("to=\"%s\"\n", mp->to ? mp->to->str_text : "")); trace(("create=%d\n", mp->create)); assert(mp->create); assert(mp->remove); assert(mp->from); assert(mp->to); batch_moved.push_back(nstring(mp->from)); batch_moved.push_back(nstring(mp->to)); } move_xargs ( nstring(project_name), change_number, batch_moved, dd, trace_options ); move_list_destructor(&files_moved); // // now extract each file from the input // trace(("extract each file\n")); cp = change_alloc(pp, change_number); change_bind_existing(cp); os_become_orig(); for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src_data; output::pointer ofp; int need_whole_source; // verbose progress message here? src_data = change_set->src->list[j]; trace_string(src_data->file_name->str_text); switch (src_data->action) { case file_action_insulate: case file_action_remove: case file_action_transparent: #ifndef DEBUG default: #endif continue; case file_action_create: case file_action_modify: break; } assert(src_data->file_name); // // If the file in the change set has been renamed to match the // local project we must look in the archive using the old // name, not the new one. // nstring *old = old_name.query(src_data->file_name); switch (src_data->usage) { case file_usage_build: continue; case file_usage_config: case file_usage_test: case file_usage_manual_test: could_have_a_trojan = 1; break; case file_usage_source: break; } archive_name.clear(); ifp = cpio_p->child(archive_name); if (!ifp.is_open()) { missing_file ( cpio_p, nstring::format ( "patch/%s", old ? old->c_str() : src_data->file_name->str_text ) ); } assert(archive_name); need_whole_source = 1; if (archive_name.starts_with("patch/")) { // // Check to see we got the file we expected // { nstring s1 = nstring::format("patch/%s", src_data->file_name->str_text);; nstring s2; if (old) s2 = "patch/" + *old; assert(archive_name == s1 || archive_name == s2); } bool patch_is_empty = (ifp->length() == 0); // // Put the patch into a file, so that we can invoke the // patch command. It is safe to assume that the patch is in // the correct format to use the patch -p0 option. // nstring patch_file_name = nstring(os_edit_filename(0)); if (!patch_is_empty) { output::pointer os = output_file::binary_open(patch_file_name); os << ifp; } ifp.close(); // // We have a patch file, but we also know that a // complete source follows. We can apply the patch // or discard it. If we fail to apply it cleanly, // we can always use the complete source which follows. // switch (src_data->action) { case file_action_remove: case file_action_insulate: case file_action_transparent: break; case file_action_create: // // We handle only the create half of the rename. // if (!src_data->move) break; // Fall through... case file_action_modify: if (!use_patch) break; // // Empty patches always apply cleanly. // if (patch_is_empty) { need_whole_source = 0; break; } // // Apply the patch. // nstring command = nstring::format ( CONF_PATCH " -p0" #ifdef HAVE_GNU_PATCH " -f -s" #endif " %s %s", src_data->file_name->str_text, patch_file_name.c_str() ); int flags = OS_EXEC_FLAG_NO_INPUT; // + OS_EXEC_FLAG_SILENT; int n = os_execute_retcode(command, flags, dd); if (n == 0) { // // The patch applied cleanly. // need_whole_source = 0; } else { sub_context_ty sc; sc.var_set_string("File_Name", src_data->file_name); sc.error_intl(i18n("warning: $filename patch not used")); } break; } if (!patch_is_empty) os_unlink(patch_file_name); // // The src file should be next. // archive_name.clear(); ifp = cpio_p->child(archive_name); if (!ifp.is_open()) { missing_file ( cpio_p, nstring::format ( "src/%s", old ? old->c_str() : src_data->file_name->str_text ) ); } assert(archive_name); } // make sure we have the file we expected { nstring s1 = nstring::format("src/%s", src_data->file_name->str_text); nstring s2; if (old) s2 = "src/" + *old; if (archive_name != s1 && (!s2 || archive_name != s2)) wrong_file(ifp, s1); } output::pointer os; if (need_whole_source) os = output_file::binary_open(src_data->file_name); else os = output_bit_bucket::create(); os << ifp; ifp.close(); } // // should be at end of input // archive_name.clear(); ifp = cpio_p->child(archive_name); if (ifp.is_open()) cpio_p->fatal_error("archive too long"); delete cpio_p; cpio_p = 0; os_become_undo(); // // Now chmod the executable files, // and set the file attributes. // exec_mode = 0755 & ~change_umask(cp); non_exec_mode = exec_mode & ~0111; os_become_orig(); for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src_data; src_data = change_set->src->list[j]; if (src_data->attribute) { fattr_ty *fattr_data; string_ty *s2; fattr_data = (fattr_ty *)fattr_type.alloc(); fattr_data->attribute = attributes_list_copy(src_data->attribute); // // We remove the edit-origin-UUID from the file's // attributes list. It may be not correct for the local // repository. // attributes_list_remove(fattr_data->attribute, EDIT_ORIGIN_UUID); fattr_write_file(attribute_file_name, fattr_data, 0); fattr_type.free(fattr_data); s2 = str_quote_shell(src_data->file_name); nstring s = nstring::format ( "aegis --file-attributes --change=%ld --project=%s " "--file=%s --verbose %s --base-rel%s", magic_zero_decode(change_number), project_name->str_text, attribute_file_name->str_text, s2->str_text, trace_options.c_str() ); str_free(s2); os_execute(s, OS_EXEC_FLAG_INPUT, dd); os_unlink_errok(attribute_file_name); } switch (src_data->action) { case file_action_create: if (ignore_uuid <= 0 && src_data->uuid && !src_data->move) { // // The incoming change set specifies a UUID for this // file, run "aefa -uuid" to set it. // string_ty *qfn = str_quote_shell(src_data->file_name); nstring s = nstring::format ( "aegis --file-attributes --uuid %s --change=%ld " "--project=%s --verbose %s --base-rel%s", src_data->uuid->str_text, magic_zero_decode(change_number), project_name->str_text, qfn->str_text, trace_options.c_str() ); str_free(qfn); os_execute(s, OS_EXEC_FLAG_INPUT, dd); } break; case file_action_modify: break; case file_action_remove: case file_action_insulate: case file_action_transparent: #ifndef DEBUG default: #endif continue; } switch (src_data->usage) { case file_usage_source: case file_usage_config: case file_usage_test: case file_usage_manual_test: break; case file_usage_build: continue; } assert(src_data->file_name); os_chmod ( src_data->file_name, (src_data->executable ? exec_mode : non_exec_mode) ); } os_become_undo(); str_free(attribute_file_name); // // Now check to see if any of them were config files. We couldn't do // it before now, in case we got an inconsistent config combination. // config_seen = 0; for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *src_data; src_data = change_set->src->list[j]; switch (src_data->action) { case file_action_create: case file_action_modify: break; case file_action_remove: case file_action_insulate: case file_action_transparent: #ifndef DEBUG default: #endif continue; } switch (src_data->usage) { case file_usage_build: continue; case file_usage_source: assert(src_data->file_name); if (change_file_is_config(cp, src_data->file_name)) { could_have_a_trojan = 1; config_seen = 1; } break; case file_usage_config: could_have_a_trojan = 1; config_seen = 1; break; case file_usage_test: case file_usage_manual_test: could_have_a_trojan = 1; break; } } change_free(cp); cp = 0; // // If the change could have a trojan horse in it, stop here with // a warning. The user needs to look at it and check. // if (trojan > 0) could_have_a_trojan = 1; else if (trojan == 0) { error_intl(0, i18n("warning: potential trojan, proceeding anyway")); could_have_a_trojan = 0; config_seen = 0; } // // If the change could have a trojan horse in the project config // file, stop here with a warning. Don't even difference the // change, because the trojan could be embedded in the diff // command. The user needs to look at it and check. // // FIX ME: what if the aecpu got rid of it? // if (config_seen) { // // before we bail, see if we can throw everything away. // if (ignore_uuid <= 0) { change::pointer other_cp = local_inventory.query(incoming_uuids); if (other_cp) { cp = change_alloc(pp, change_number); change_bind_existing(cp); if (nothing_has_changed(cp, up)) { nstring attr = nstring::format ( "%s+=%s", ORIGINAL_UUID, change_set->uuid->str_text ); nstring s2 = nstring::format ( "aegis -change-attr %s -change=%ld -project=%s%s", attr.quote_shell().c_str(), magic_zero_decode(other_cp->number), project_name_get(other_cp->pp)->str_text, trace_options.c_str() ); os_become_orig(); os_execute(s2, OS_EXEC_FLAG_INPUT | OS_EXEC_FLAG_ERROK, dd); os_become_undo(); goto cancel_the_change; } change_free(cp); cp = 0; } } error_intl ( 0, i18n("warning: potential trojan, review before completing development") ); // // We set now the uuid of the change so it is preserved. // change_uuid_set ( nstring(project_name), change_number, nstring(change_set->uuid), trace_options, dd ); // // Make sure we are using an appropriate architecture. This is // one of the commonest problems when seeding an empty repository. // nstring s = nstring::format ( "aegis --change-attr --fix-arch --change=%ld --project=%s%s", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(s, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); return; } // // now merge the change // { nstring merge_command = nstring::format ( "aegis --diff --only-merge --change=%ld --project=%s --verbose", magic_zero_decode(change_number), project_name->str_text ); os_become_orig(); os_execute(merge_command, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); } // // Now that all the files have been unpacked, // set the change's UUID. // // It is vaguely possible you have already downloaded this change // before, so we don't complain (OS_EXEC_FLAG_ERROK) if the command // fails. // // We intentionally set the UUID before the aecpu -unch. The aecpu // -unch will clear the UUID if the change's file inventory changes, // because it is no longer the same change set. // if (change_set->uuid) { change_uuid_set ( nstring(project_name), change_number, nstring(change_set->uuid), trace_options, dd ); } // // If we are receiving a change set with an UUID, and the user has // not used the --ignore-uuid option, we avoid the uncopy files so // the UUID is preserved and we don't need a new one. This should // reduce the UUID proliferation and some related side effects. // if (change_set->uuid && ignore_uuid <= 0) uncopy = 0; // // Un-copy any files which did not change. // // The idea is, if there are no files left, there is nothing // for this change to do, so cancel it. // // We intentionally set the UUID before the aecpu -unch. The aecpu // -unch will clear the UUID if the change's file inventory changes, // because it is no longer the same change set. // if (uncopy) { { nstring copy_file_undo_cmd = nstring::format ( "aegis --copy-file-undo --unchanged --change=%ld " "--project=%s --verbose%s", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(copy_file_undo_cmd, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); } // // If there are no files left, we already have this change. // if (number_of_files(project_name, change_number) == 0) { cancel_the_change: // // get out of there // os_chdir(dot); // // stop developing the change // cancel the change // nstring s = nstring::format ( "aegis --develop-begin-undo --change=%ld --project=%s " "--verbose%s --new-change-undo", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(s, OS_EXEC_FLAG_INPUT, dot); // // run away, run away! // error_intl(0, i18n("change already present")); return; } } else if (ignore_uuid <= 0) { // // If this change set actually changes nothing (all the file // contents are already the same as in the project) and the // change set UUID is already in the project, then get rid of // this change set, and add the UUID to the existing change. // change::pointer other_cp = local_inventory.query(incoming_uuids); if (other_cp) { cp = change_alloc(pp, change_number); change_bind_existing(cp); if (nothing_has_changed(cp, up)) { nstring attr = nstring::format ( "%s+=%s", ORIGINAL_UUID, change_set->uuid->str_text ); nstring s2 = nstring::format ( "aegis -change-attr %s -change=%ld -project=%s%s", attr.quote_shell().c_str(), magic_zero_decode(other_cp->number), project_name_get(other_cp->pp)->str_text, trace_options.c_str() ); os_become_orig(); os_execute(s2, OS_EXEC_FLAG_INPUT | OS_EXEC_FLAG_ERROK, dd); os_become_undo(); goto cancel_the_change; } change_free(cp); cp = 0; } } // // now diff the change // { nstring diff_command = nstring::format ( "aegis --diff --no-merge --change=%ld --project=%s --verbose%s", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(diff_command, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); } // // If the change could have a trojan horse in it, stop here with // a warning. The user needs to look at it and check. // if (could_have_a_trojan) { error_intl ( 0, i18n("warning: potential trojan, review before completing development") ); return; } // // Sleep for a second to make sure the derived files will have // mod-times strictly later than the source files, and that the aeb // timestamp will also be strictly later then the mod times for the // source files. // sleep(1); // // now build the change // { nstring build_command = nstring::format ( "aegis --build --change=%ld --project=%s --verbose%s", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(build_command, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); } // // Sleep for a second to make sure the aet timestamps will be // strictly later then the aeb timestamp. // sleep(1); // // re-read the change state data. // cp = change_alloc(pp, change_number); change_bind_existing(cp); cstate_ty *cstate_data = cp->cstate_get(); // // now test the change // if (need_to_test && !cstate_data->test_exempt) { nstring s = nstring::format ( "aegis --test --change=%ld --project=%s --verbose%s", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(s, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); } if (need_to_test && !cstate_data->test_baseline_exempt) { nstring s = nstring::format ( "aegis --test --baseline --change=%ld --project=%s " "--verbose%s", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(s, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); } // always to a regession test? if (!cstate_data->regression_test_exempt) { nstring s = nstring::format ( "aegis --test --regression --change=%ld --project=%s " "--verbose%s", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(s, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); } change_free(cp); cp = 0; project_free(pp); pp = 0; // // end development (if we got this far!) // nstring s = nstring::format ( "aegis --develop-end --change=%ld --project=%s --verbose%s", magic_zero_decode(change_number), project_name->str_text, trace_options.c_str() ); os_become_orig(); os_execute(s, OS_EXEC_FLAG_INPUT, dd); os_become_undo(); // verbose success message here? }