// // aegis - project change supervisor // Copyright (C) 2005-2008, 2011, 2012, 2014 Peter Miller, // Copyright (C) 2004, 2005, 2007-2009 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 #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 nstring fix_compatibility_modifier(const nstring &uri, bool use_compat) { trace(("fix_compatibility_modifier(uri = %s)\n{\n", uri.quote_c().c_str())); if ( 0 != memcmp("http:", uri.c_str(), 5) && 0 != memcmp("https:", uri.c_str(), 6) ) { // Only mess with HTTP URLs trace(("return %s;\n", uri.quote_c().c_str())); trace(("}\n")); return uri; } if (!strstr(uri.c_str(), "/cgi-bin/")) { // Only add +compat=xxx to CGI scripts trace(("return %s;\n", uri.quote_c().c_str())); trace(("}\n")); return uri; } const char *cp = strstr(uri.c_str(), "compat="); if (!cp) { if (!use_compat) return uri; nstring result(uri + "+compat=" + version_stamp()); trace(("return %s;\n", result.quote_c().c_str())); trace(("}\n")); return result; } cp += 7; nstring left(uri.c_str(), cp - uri.c_str()); while (*cp && *cp != '+') ++cp; nstring result(left + version_stamp() + cp); trace(("return %s;\n", result.quote_c().c_str())); trace(("}\n")); return result; } void replay_main(void) { string_ty *project_name = NULL; nstring ifn; nstring trojan; nstring_list exclude_uuid_list; nstring_list include_uuid_list; nstring_list exclude_version_list; nstring_list include_version_list; bool all_changes = false; bool use_compat = true; arglex(); while (arglex_token != arglex_token_eoln) { switch (arglex_token) { default: generic_argument(usage); continue; case arglex_token_project: arglex(); arglex_parse_project(&project_name, usage); continue; case arglex_token_trojan: if (!trojan.empty()) duplicate_option(usage); trojan = " --trojan"; break; case arglex_token_trojan_not: if (!trojan.empty()) duplicate_option(usage); trojan = " --no-trojan"; break; case arglex_token_file: if (!ifn.empty()) duplicate_option(usage); if (arglex() != arglex_token_string) { option_needs_file(arglex_token_file, usage); // NOTREACHED } ifn = arglex_value.alv_string; break; case arglex_token_exclude_uuid: switch (arglex()) { default: option_needs_string(arglex_token_exclude_uuid, usage); // NOTREACHED case arglex_token_string: exclude_uuid_list.push_back(arglex_value.alv_string); break; } break; case arglex_token_exclude_uuid_not: switch (arglex()) { default: option_needs_string(arglex_token_exclude_uuid_not, usage); // NOTREACHED case arglex_token_string: include_uuid_list.push_back(arglex_value.alv_string); break; } break; case arglex_token_exclude_version: switch (arglex()) { default: option_needs_string(arglex_token_exclude_version, usage); // NOTREACHED case arglex_token_string: exclude_version_list.push_back(arglex_value.alv_string); break; } break; case arglex_token_exclude_version_not: switch (arglex()) { default: option_needs_string(arglex_token_exclude_version_not, usage); // NOTREACHED case arglex_token_string: include_version_list.push_back(arglex_value.alv_string); break; } break; case arglex_token_persevere: case arglex_token_persevere_not: user_ty::persevere_argument(usage); break; case arglex_token_maximum: all_changes = true; break; case arglex_token_compatibility_not: use_compat = false; } arglex(); } trace_nstring(ifn); if (ifn.empty()) option_needs_url(arglex_token_file, usage); // // locate project data // if (!project_name) { nstring n = user_ty::create()->default_project(); project_name = n.get_ref_copy(); } project *pp = project_alloc(project_name); pp->bind_existing(); user_ty::pointer up = user_ty::create(); symtab local_inventory; bool include_branches = true; bool ignore_original_uuid = false; change_functor_inventory_builder cf(include_branches, all_changes, ignore_original_uuid, pp, &local_inventory); project_inventory_walk(pp, cf); // // Parse the input file name to work out whether it is a file name // or a URL. This is difficult, and made worse by the possibilities // that it could be a simple file name or a simple hostname. // url smart_url(ifn); if (smart_url.is_a_file()) ifn = smart_url.get_path(); else { smart_url.set_path_if_empty ( nstring::format("cgi-bin/aeget/%s", project_name_get(pp).c_str()) ); smart_url.set_query_if_empty("inventory"); ifn = smart_url.reassemble(); } trace_nstring(ifn); // // Open the file (or URL) containing the inventory. // os_become_orig(); input::pointer ifp = input_file::open(ifn); ifp = input_bunzip2::create_if_candidate(ifp); ifp = input_gunzip::create_if_candidate(ifp); os_become_undo(); nstring_list remote_change; for (;;) { nstring line; os_become_orig(); bool ok = ifp->one_line(line); os_become_undo(); if (!ok) break; replay_line parts; if (!parts.extract(line)) continue; change::pointer cp = local_inventory.query(parts.get_uuid()); if (cp) continue; // // we exclude from further processing: // 1) UUIDs specified by --exclude-uuid, the match must be // exact; // 2) versions specified by --exclude-version, the match is // against a glob pattern. // if (exclude_uuid_list.member(parts.get_uuid())) continue; if (exclude_version_list.gmatch_candidate(parts.get_version())) continue; // // we exclude from further processing: // 1) UUIDs NOT specified by --include--uuid, the match must // be exact; // 2) versions NOT specified by --include-version, the match // is against a glob pattern. // if ( !include_uuid_list.empty() && !include_uuid_list.member(parts.get_uuid()) ) continue; if ( !include_version_list.empty() && !include_version_list.gmatch_candidate(parts.get_version()) ) continue; remote_change.push_back_unique(parts.get_url2()); } trace(("remote_change.size() = %zd;\n", remote_change.size())); os_become_orig(); nstring dot(os_curdir()); os_become_undo(); // // Receive the changes: // 1) create an absolute URL to download the archive from // 2) run aedist -receive // 3) integrate the change, if possible. // for (size_t c = 0; c < remote_change.size(); ++c) { project *pp2 = project_alloc(project_name); pp2->bind_existing(); // // The URL as present in the change set inventory is not // absolute so we must create one. // nstring url_abs = remote_change[c]; if (!url_abs.downcase().starts_with("http://")) { url relative(url_abs); relative.set_host_part_from(smart_url); url_abs = relative.reassemble(); } // // There could be a compat=n.nn modifier in the // URL, if so replace it, otherwise add one. // url_abs = fix_compatibility_modifier(url_abs, use_compat); // // Start building the command. // nstring trace_options(trace_args()); nstring change_number_file(os_edit_filename(0)); nstring aedist_cmd = nstring::format ( "aedist%s -receive -project=%s -output=%s -file %s%s%s%s", trace_args(), nstring(project_name_get(pp2)).quote_shell().c_str(), change_number_file.c_str(), url_abs.quote_shell().c_str(), trojan.c_str(), trace_options.c_str(), (option_verbose_get() ? " --verbose" : "") ); trace_nstring(aedist_cmd); os_become_orig(); int rc = os_execute_retcode ( aedist_cmd.get_ref(), OS_EXEC_FLAG_INPUT, dot.get_ref() ); os_become_undo(); pp2->free(); pp2 = 0; trace_int(rc); if (rc && !up->persevere_preference(false)) quit(1); pp2 = project_alloc(project_name); pp2->bind_existing(); // // Read the change number from the file, there is no more need // to guess the right value in advance since it's now written // by aedist -rec itself. // os_become_orig(); input::pointer ifp2 = input_file::open(change_number_file, true); nstring s; if (!ifp2->one_line(s) || s.empty()) ifp2->fatal_error("short file"); ifp2.reset(); os_become_undo(); long change_number = s.to_long(); change::pointer cp = change::create(pp2, change_number); int change_exists = cp->bind_existing_errok(); trace_int(change_exists); if (!change_exists) continue; assert(cp); cstate_ty *cstate_data = cp->cstate_get(); assert(cstate_data); trace(("state = %s;\n", cstate_state_ename(cstate_data->state))); switch (cstate_data->state) { case cstate_state_awaiting_integration: break; case cstate_state_being_integrated: case cstate_state_completed: assert(0); // FALLTHROUGH case cstate_state_awaiting_development: case cstate_state_being_developed: case cstate_state_awaiting_review: case cstate_state_being_reviewed: #ifndef DEBUG default: #endif if (!up->persevere_preference(false)) quit(0); continue; } assert(cstate_data->state == cstate_state_awaiting_integration); nstring aeintq_cmd = nstring::format ( "aeintegratq -p %s -c %ld", nstring(project_name_get(pp2)).quote_shell().c_str(), change_number ); trace_nstring(aeintq_cmd); os_become_orig(); rc = os_execute_retcode ( aeintq_cmd.get_ref(), OS_EXEC_FLAG_INPUT, dot.get_ref() ); os_become_undo(); cp->lock_sync_forced(); cstate_data = cp->cstate_get(); assert(cstate_data); trace(("state = %s;\n", cstate_state_ename(cstate_data->state))); switch (cstate_data->state) { case cstate_state_completed: break; case cstate_state_awaiting_development: case cstate_state_awaiting_review: case cstate_state_being_reviewed: case cstate_state_awaiting_integration: case cstate_state_being_integrated: case cstate_state_being_developed: #ifndef DEBUG default: #endif if (!up->persevere_preference(false)) quit(0); continue; } assert(cstate_data->state == cstate_state_completed); pp2->free(); pp2 = 0; } } // vim: set ts=8 sw=4 et :