// // aegis - project change supervisor // Copyright (C) 1999-2008 Peter Miller // Copyright (C) 2004, 2005, 2007, 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 #include #include #include #include #define NO_TIME_SET ((time_t)(-1)) static bool use_uuid; static bool have_it_already(cstate_ty *change_set, fstate_src_ty *src_data) { size_t j; cstate_src_ty *dst_data; if (!change_set->src) return false; // // first we look by uuid, we *must* use also the name in order to // properly handle renames. With renames we have 2 entries with // the same UUID. // if (src_data->uuid) { for (j = 0; j < change_set->src->length; ++j) { dst_data = change_set->src->list[j]; if ( dst_data->uuid && str_equal(dst_data->uuid, src_data->uuid) && str_equal(dst_data->file_name, src_data->file_name) ) return true; } } // second we look by name for (j = 0; j < change_set->src->length; ++j) { dst_data = change_set->src->list[j]; if (str_equal(dst_data->file_name, src_data->file_name)) return true; } return false; } static void one_more_src(project_file_roll_forward &historian, cstate_ty *change_set, fstate_src_ty *src_data, int use_attr) { cstate_src_ty **dst_data_p; cstate_src_ty *dst_data; meta_type *type_p = 0; trace(("add \"%s\" %s %s\n", src_data->file_name->str_text, file_action_ename(src_data->action), file_usage_ename(src_data->usage))); if (!change_set->src) change_set->src = (cstate_src_list_ty *)cstate_src_list_type.alloc(); dst_data_p = (cstate_src_ty **) cstate_src_list_type.list_parse(change_set->src, &type_p); assert(type_p == &cstate_src_type); dst_data = (cstate_src_ty *)cstate_src_type.alloc(); *dst_data_p = dst_data; dst_data->file_name = str_copy(src_data->file_name); dst_data->action = src_data->action; dst_data->usage = src_data->usage; dst_data->executable = src_data->executable; if (src_data->move) dst_data->move = str_copy(src_data->move); if (use_attr) { if (src_data->attribute) dst_data->attribute = attributes_list_copy(src_data->attribute); if (use_uuid && src_data->uuid) dst_data->uuid = str_copy(src_data->uuid); // // If the historian is not set we can not retrieve the needed // information, so we return. // trace(("historian = %d;\n", historian.is_set() ? 1 : 0)); if (!historian.is_set()) return; // // We handle only modified files. // // todo: // We should handle also file renames because the rename // operation may carry also file modifications. However the // aemv command does not support the -delta option, yet. // trace(("src_data->action = %s;\n", file_action_ename(src_data->action))); switch (src_data->action) { case file_action_modify: break; case file_action_insulate: assert(0); // FALLTHROUGH case file_action_create: case file_action_transparent: case file_action_remove: #ifndef DEBUG default: #endif return; break; } trace(("fn=\"%s\";\nft = %s;\n", src_data->file_name->str_text, file_action_ename(src_data->action))); assert(historian.is_set()); file_event *fep = historian.get_older(src_data->file_name); // // The fep *can* be NULL if the change is not completed // (e.g. for renamed files). At the moment we assume this is // not the case. // if (!fep) return; assert(fep); if (!fep->get_change()->cstate_data->uuid || !use_uuid) return; if (!src_data->edit_origin) return; assert(src_data->edit_origin->revision); assert(fep->get_src()); if (!fep->get_src()->edit->revision) return; trace_string(fep->get_change()->cstate_data->uuid->str_text); if ( !str_equal ( src_data->edit_origin->revision, fep->get_src()->edit->revision ) ) { // // It's tempting to say // // assert(0); // // but the assumption is not true for outstanding changes. // return; } if (!dst_data->attribute) dst_data->attribute = (attributes_list_ty*)attributes_list_type.alloc(); attributes_list_append ( dst_data->attribute, EDIT_ORIGIN_UUID, fep->get_change()->cstate_data->uuid->str_text ); } } static int cmp(const void *va, const void *vb) { cstate_src_ty *a; cstate_src_ty *b; a = *(cstate_src_ty **)va; b = *(cstate_src_ty **)vb; return strcmp(a->file_name->str_text, b->file_name->str_text); } static int len_printable(string_ty *s, int max_len) { const char *cp; int result; if (!s) return 0; // Intentionally the C locale, not the user's locale for (cp = s->str_text; *cp && isprint((unsigned char)*cp); ++cp) ; result = (cp - s->str_text); if (result > max_len) result = max_len; return result; } void send_main(void) { int use_attributes; int use_patch; int use_change_number; int use_config; int use_rename_patch; string_ty *project_name; long change_number; const char *branch; int grandparent; int trunk; output::pointer ofp; project_ty *pp; change::pointer cp; user_ty::pointer up; cstate_ty *cstate_data; string_ty *output_filename; cstate_ty *change_set; time_t when; size_t j; int description_header; int baseline; int entire_source; content_encoding_t ascii_armor; string_ty *dev_null; string_ty *diff_output_filename; long delta_number; time_t delta_date; const char *delta_name; const char *compatibility; bool undocumented_testing_flag = false; arglex(); compatibility = 0; branch = 0; change_number = 0; grandparent = 0; project_name = 0; trunk = 0; output_filename = 0; description_header = -1; baseline = 0; entire_source = -1; ascii_armor = content_encoding_unset; compression_algorithm_t needs_compression = compression_algorithm_not_set; delta_date = NO_TIME_SET; delta_number = -1; delta_name = 0; int use_mime_header = -1; int ignore_uuid = -1; while (arglex_token != arglex_token_eoln) { switch (arglex_token) { default: generic_argument(usage); continue; case arglex_token_baseline: if (baseline) duplicate_option(usage); baseline = 1; break; case arglex_token_entire_source: if (entire_source > 0) duplicate_option(usage); if (entire_source >= 0) { mutually_exclusive_options ( arglex_token_entire_source, arglex_token_entire_source_not, usage ); } entire_source = 1; break; case arglex_token_entire_source_not: if (entire_source == 0) duplicate_option(usage); if (entire_source >= 0) { mutually_exclusive_options ( arglex_token_entire_source, arglex_token_entire_source_not, usage ); } entire_source = 0; break; case arglex_token_change: case arglex_token_delta_from_change: arglex(); // fall through... case arglex_token_number: arglex_parse_change(&project_name, &change_number, usage); continue; case arglex_token_project: arglex(); arglex_parse_project(&project_name, usage); continue; case arglex_token_branch: if (branch) duplicate_option(usage); switch (arglex()) { default: option_needs_number(arglex_token_branch, usage); case arglex_token_number: case arglex_token_string: branch = arglex_value.alv_string; break; } break; case arglex_token_trunk: if (trunk) duplicate_option(usage); ++trunk; break; case arglex_token_grandparent: if (grandparent) duplicate_option(usage); ++grandparent; break; case arglex_token_output: if (output_filename) duplicate_option(usage); switch (arglex()) { default: option_needs_file(arglex_token_output, usage); // NOTREACHED case arglex_token_stdio: output_filename = str_from_c(""); break; case arglex_token_string: output_filename = str_from_c(arglex_value.alv_string); break; } break; case arglex_token_description_header: if (description_header == 1) duplicate_option(usage); else if (description_header >= 0) { head_desc_yuck: mutually_exclusive_options ( arglex_token_description_header, arglex_token_description_header_not, usage ); } description_header = 1; break; case arglex_token_description_header_not: if (description_header == 0) duplicate_option(usage); else if (description_header >= 0) goto head_desc_yuck; description_header = 0; break; case arglex_token_ascii_armor: if (ascii_armor != content_encoding_unset) { duplicate_option_by_name ( arglex_token_content_transfer_encoding, usage ); } ascii_armor = content_encoding_base64; break; case arglex_token_ascii_armor_not: if (ascii_armor != content_encoding_unset) { duplicate_option_by_name ( arglex_token_content_transfer_encoding, usage ); } ascii_armor = content_encoding_none; break; case arglex_token_mime_header: if (use_mime_header >= 0) duplicate_option(usage); use_mime_header = 1; break; case arglex_token_mime_header_not: if (use_mime_header >= 0) duplicate_option(usage); use_mime_header = 0; break; case arglex_token_content_transfer_encoding: if (ascii_armor != content_encoding_unset) duplicate_option(usage); if (arglex() != arglex_token_string) { option_needs_string ( arglex_token_content_transfer_encoding, usage ); } ascii_armor = content_encoding_grok(arglex_value.alv_string); break; case arglex_token_compress: if (needs_compression != compression_algorithm_not_set) { duplicate_option_by_name ( arglex_token_compression_algorithm, usage ); } needs_compression = compression_algorithm_unspecified; break; case arglex_token_compress_not: if (needs_compression != compression_algorithm_not_set) { duplicate_option_by_name ( arglex_token_compression_algorithm, usage ); } needs_compression = compression_algorithm_none; break; case arglex_token_compression_algorithm: if (arglex() != arglex_token_string) { option_needs_string(arglex_token_compression_algorithm, usage); // NOTREACHED } else { compression_algorithm_t temp = compression_algorithm_by_name(arglex_value.alv_string); // // We don't complain if the answer is going to be the same, // for compatibility with the old options. // if (temp == needs_compression) break; switch (needs_compression) { case compression_algorithm_not_set: case compression_algorithm_unspecified: needs_compression = temp; break; case compression_algorithm_none: case compression_algorithm_gzip: case compression_algorithm_bzip2: duplicate_option_by_name ( arglex_token_compression_algorithm, usage ); // NOTREACHED } } break; case arglex_token_delta: if (delta_number >= 0 || delta_name) duplicate_option(usage); switch (arglex()) { default: option_needs_number(arglex_token_delta, usage); // NOTREACHED case arglex_token_number: delta_number = arglex_value.alv_number; if (delta_number < 0) { sub_context_ty *scp; scp = sub_context_new(); sub_var_set_long(scp, "Number", delta_number); fatal_intl(scp, i18n("delta $number out of range")); // NOTREACHED sub_context_delete(scp); } break; case arglex_token_string: delta_name = arglex_value.alv_string; break; } break; case arglex_token_delta_date: if (delta_date != NO_TIME_SET) duplicate_option(usage); if (arglex() != arglex_token_string) { option_needs_string(arglex_token_delta_date, usage); // NOTREACHED } delta_date = date_scan(arglex_value.alv_string); if (delta_date == NO_TIME_SET) fatal_date_unknown(arglex_value.alv_string); break; case arglex_token_patch: { sub_context_ty *scp; scp = sub_context_new(); sub_var_set_charstar ( scp, "Name1", arglex_token_name(arglex_token_patch) ); sub_var_set_charstar ( scp, "Name2", arglex_token_name(arglex_token_compatibility) ); error_intl ( scp, i18n("warning: $name1 obsolete, use $name2 option") ); sub_context_delete(scp); if (compatibility) duplicate_option_by_name(arglex_token_compatibility, usage); compatibility = "99999999"; } break; case arglex_token_patch_not: { sub_context_ty *scp; scp = sub_context_new(); sub_var_set_charstar ( scp, "Name1", arglex_token_name(arglex_token_patch) ); sub_var_set_format ( scp, "Name2", "%s=4.6", arglex_token_name(arglex_token_compatibility) ); error_intl ( scp, i18n("warning: $name1 obsolete, use $name2 option") ); sub_context_delete(scp); if (compatibility) duplicate_option_by_name(arglex_token_compatibility, usage); compatibility = "4.6"; } break; case arglex_token_compatibility: if (compatibility) duplicate_option(usage); switch (arglex()) { case arglex_token_string: case arglex_token_number: compatibility = arglex_value.alv_string; break; default: option_needs_string(arglex_token_compatibility, usage); // NOTREACHED } break; case arglex_token_signed_off_by: case arglex_token_signed_off_by_not: option_signed_off_by_argument(usage); 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; case arglex_token_test: // This option may be used by the test suite to insert // zero-valued timestamps into the .ae file, so that // automated tests don't get false negatives just because // the timestamp has changed. undocumented_testing_flag = true; break; } arglex(); } if (entire_source < 0) entire_source = baseline; // // Translate the compatibility version number into a set of // capabilities. // use_patch = 1; use_change_number = 1; use_config = 1; use_attributes = 1; // implies UUIDs as well use_rename_patch = 1; bool use_bzip2 = true; if (compatibility) { // // FIXME: should we check that it actually looks like a version // string? // // // Patches were introduced in Peter's 4.6.D022, // publicly in 4.7 // use_patch = (strverscmp(compatibility, "4.7") >= 0); // // Change numbers were introduced in Peter's 4.9.D035, // publicly in 4.10 // use_change_number = (strverscmp(compatibility, "4.10") >= 0); // // The config file usage was introduced in Peter's 4.16.D024, // publicly in 4.17 // use_config = (strverscmp(compatibility, "4.17") >= 0); // // The file attributes and change attributes were added to // aedist -send in Peter's 4.16.D054, publicly in 4.17 // // The change UUIDs were added to // aedist -send in Peter's 4.16.D089, publicly in 4.17 // use_attributes = use_config; if (!use_attributes && ignore_uuid < 0) ignore_uuid = 1; // // The patch for renamed files were added to aedist -send in // Peter's 4.18.D004, publicly in 4.19 // use_rename_patch = (strverscmp(compatibility, "4.19") >= 0); if (use_mime_header < 0) { // This was available in Peter's 4.21.Dnnn, publicly in 4.22 use_mime_header = (strverscmp(compatibility, "4.22") >= 0); } // // Use the bzip compression algorithm. // Peter's 4.21.D148, publicly in 4.22 // use_bzip2 = (strverscmp(compatibility, "4.22") >= 0); // // Add new compatibility tests above this comment. // // If you add anything more to this set of flags, you MUST // also update the aeget/get/change/download.cc file, so that // downloads are possible. // } if (entire_source) { use_patch = 0; use_rename_patch = 0; } // // reject illegal combinations of options // if (change_number && baseline) { mutually_exclusive_options ( arglex_token_change, arglex_token_baseline, usage ); } if (grandparent) { if (branch) { mutually_exclusive_options ( arglex_token_branch, arglex_token_grandparent, usage ); } if (trunk) { mutually_exclusive_options ( arglex_token_trunk, arglex_token_grandparent, usage ); } branch = ".."; } if (trunk) { if (branch) { mutually_exclusive_options ( arglex_token_branch, arglex_token_trunk, usage ); } branch = ""; } if ( ( (delta_name || delta_number >= 0) + !!change_number + (delta_date != NO_TIME_SET) ) > 1 ) { mutually_exclusive_options3 ( arglex_token_delta, arglex_token_delta_date, arglex_token_change, usage ); } use_uuid = (ignore_uuid <= 0); // // locate project data // if (!project_name) { nstring n = user_ty::create()->default_project(); project_name = str_copy(n.get_ref()); } pp = project_alloc(project_name); str_free(project_name); pp->bind_existing(); // // locate the other branch // if (branch) pp = pp->find_branch(branch); // // locate user data // up = user_ty::create(); // // it is an error if the delta does not exist // if (delta_name) { string_ty *s1; s1 = str_from_c(delta_name); change_number = project_history_change_by_name(pp, s1, 0); str_free(s1); delta_name = 0; } when = now(); if (delta_date != NO_TIME_SET) { // // If the time is in the future, you could get a different // answer for the same input at some point in the future. // // This is the "time safe" quality first described by // Damon Poole // if (delta_date > when) project_error(pp, 0, i18n("date in the future")); // // Now find the change number corresponding. // change_number = project_history_change_by_timestamp(pp, delta_date); } if (delta_number >= 0) { // does not return if no such delta number change_number = project_history_change_by_delta(pp, delta_number); delta_number = 0; } // // locate change data // if (baseline) cp = change_copy(pp->change_get()); else { if (!change_number) change_number = up->default_change(pp); cp = change_alloc(pp, change_number); change_bind_existing(cp); } // // If the user asked for one, append a Signed-off-by line to this // change's description. (Since we don't write the cstate back out, // it is safe to change the change's description.) // if (option_signed_off_by_get(false)) change_signed_off_by(cp, up); // // Check the change state. // cstate_data = cp->cstate_get(); project_file_roll_forward historian; switch (cstate_data->state) { case cstate_state_awaiting_development: #ifndef DEBUG default: #endif change_fatal(cp, 0, i18n("bad send state")); // NOTREACHED case cstate_state_being_integrated: case cstate_state_awaiting_integration: case cstate_state_being_reviewed: case cstate_state_awaiting_review: case cstate_state_being_developed: break; case cstate_state_completed: // // Need to reconstruct the appropriate file histories even for // outstanding changes because some file may be renamed and we // need to extract the old file from the baseline. // historian.set ( pp, ( (delta_date != NO_TIME_SET) ? delta_date : change_completion_timestamp(cp) ), 0 ); break; } // open the output os_become_orig(); if (ascii_armor == content_encoding_unset) ascii_armor = content_encoding_base64; switch (needs_compression) { case compression_algorithm_not_set: if (ascii_armor == content_encoding_none) { needs_compression = compression_algorithm_none; break; } // Fall through... case compression_algorithm_unspecified: needs_compression = ( use_bzip2 ? compression_algorithm_bzip2 : compression_algorithm_gzip ); break; case compression_algorithm_none: case compression_algorithm_gzip: case compression_algorithm_bzip2: break; } if (ascii_armor == content_encoding_none) { ofp = output_file::binary_open(output_filename); } else { ofp = output_file::text_open(output_filename); } // // The mime header is conditional. // if (use_mime_header < 0) use_mime_header = (ascii_armor != content_encoding_none); if (use_mime_header) { ofp->fputs("MIME-Version: 1.0\n"); ofp->fputs("Content-Type: application/aegis-change-set\n"); content_encoding_header(ofp, ascii_armor); string_ty *s1 = project_name_get(pp); string_ty *s2 = cstate_data->brief_description; if (entire_source) s2 = project_description_get(pp); ofp->fprintf ( "Subject: %.*s - %.*s\n", len_printable(s1, 40), s1->str_text, len_printable(s2, 80), s2->str_text ); if (change_number && !entire_source) { ofp->fprintf ( "Content-Name: %s.C%3.3ld.ae\n", project_name_get(pp)->str_text, change_number ); ofp->fprintf ( "Content-Disposition: attachment; filename=%s.C%3.3ld.ae\n", project_name_get(pp)->str_text, change_number ); } else { ofp->fprintf ( "Content-Name: %s.ae\n", project_name_get(pp)->str_text ); ofp->fprintf ( "Content-Disposition: attachment; filename=%s.ae\n", project_name_get(pp)->str_text ); } ofp->fputc('\n'); } ofp = output_content_encoding(ofp, ascii_armor); switch (needs_compression) { case compression_algorithm_not_set: assert(0); break; case compression_algorithm_none: break; case compression_algorithm_unspecified: assert(0); // Fall through... case compression_algorithm_gzip: ofp = output_gzip::create(ofp); break; case compression_algorithm_bzip2: ofp = output_bzip2::create(ofp); break; } time_t archive_mtime = 0; if (!undocumented_testing_flag) archive_mtime = change_completion_timestamp(cp); output_cpio *cpio_p = new output_cpio(ofp, archive_mtime); // // Add the project name to the archive. // nstring childs_name = "etc/project-name"; ofp = cpio_p->child(childs_name, -1); ofp->fprintf("%s\n", project_name_get(pp)->str_text); ofp.reset(); // // Add the change number to the archive. // if (use_change_number) { childs_name = "etc/change-number"; output::pointer op = cpio_p->child(childs_name, -1); op->fprintf("%ld\n", change_number); } os_become_undo(); // // Add the change details to the archive. // change_set = (cstate_ty *)cstate_type.alloc(); if (entire_source) { change_set->brief_description = str_copy(project_description_get(pp)); } else { change_set->brief_description = str_copy(cstate_data->brief_description); } if (description_header) { string_ty *warning; warning = 0; if ( entire_source ? cstate_data->state < cstate_state_being_integrated : cstate_data->state != cstate_state_completed ) { warning = str_format ( "Warning: the original change was in the '%s' state\n", cstate_state_ename(cstate_data->state) ); } if (cstate_data->state > cstate_state_being_developed) { cstate_history_list_ty *hlp; hlp = cstate_data->history; assert(hlp); assert(hlp->length > 0); assert(hlp->list); if (hlp && hlp->length > 0 && hlp->list) { cstate_history_ty *hp; hp = hlp->list[hlp->length - 1]; assert(hp); when = hp->when; } } change_set->description = str_format ( "From: %s\nDate: %.24s\n%s\n%s", up->get_email_address().c_str(), ctime(&when), (warning ? warning->str_text : ""), cstate_data->description->str_text ); if (warning) str_free(warning); } else if (entire_source) change_set->description = str_copy(project_description_get(pp)); else change_set->description = str_copy(cstate_data->description); change_set->cause = cstate_data->cause; change_set->test_exempt = cstate_data->test_exempt; change_set->test_baseline_exempt = cstate_data->test_baseline_exempt; change_set->regression_test_exempt = cstate_data->regression_test_exempt; if (use_attributes) { change_set->attribute = ( cstate_data->attribute ? attributes_list_copy(cstate_data->attribute) : (attributes_list_ty *)attributes_list_type.alloc() ); if (use_uuid) { change_functor_attribute_list result(false, change_set->attribute); if (change_was_a_branch(cp)) { // // For branches, add all of the constituent change sets' // UUIDs. That way, if you resynch by grabbing a whole // branch as one change set, you still grab all of the // constituent change set UUIDs. // project_inventory_walk(pp, result); } if (entire_source) { // // If they said --entire-source, add all of the accumulated // change set UUIDs. That way, if you resynch by grabbing // a whole project as one change set, you still grab all of // the constituent change set UUIDs. // time_t limit = change_completion_timestamp(cp); project_inventory_walk(pp, result, limit); } } else { for (;;) { attributes_ty *ap = attributes_list_extract ( change_set->attribute, ORIGINAL_UUID ); if (!ap) break; attributes_type.free(ap); } } if (change_set->attribute->length == 0) { attributes_list_type.free(change_set->attribute); change_set->attribute = 0; } } if (use_attributes && use_uuid && cstate_data->uuid) change_set->uuid = str_copy(cstate_data->uuid); // FIXME: architecture // FIXME: copyright years // // Scan for files to be added to the output. // bool aeget_inventory_hide = change_attributes_find_boolean(cp, "aeget:inventory:hide"); for (j = 0;; ++j) { fstate_src_ty *src_data = change_file_nth(cp, j, view_path_first); if (!src_data) break; // // we only omit local source files from change sets which are // not composed solely of omitted files. This is so that it is // possible to send a change set composed only of local sources // to another site (just not via the inventory listings). // if ( !aeget_inventory_hide && attributes_list_find_boolean ( src_data->attribute, "local-source-hide" ) ) continue; switch (src_data->usage) { case file_usage_build: switch (src_data->action) { case file_action_modify: continue; case file_action_create: case file_action_remove: case file_action_insulate: case file_action_transparent: break; } // fall through... case file_usage_source: case file_usage_config: case file_usage_test: case file_usage_manual_test: switch (src_data->action) { case file_action_create: case file_action_modify: if (cstate_data->state < cstate_state_completed) { string_ty *s = change_file_path(cp, src_data->file_name); assert(s); if (s) { os_become_orig(); src_data->executable = os_executable(s); os_become_undo(); str_free(s); } } break; case file_action_remove: break; case file_action_insulate: continue; case file_action_transparent: if ( src_data->about_to_be_created_by || src_data->about_to_be_copied_by ) continue; break; } break; } if (!use_config && src_data->usage == file_usage_config) src_data->usage = file_usage_source; one_more_src(historian, change_set, src_data, use_attributes); } if (entire_source) { if (historian.is_set()) { nstring_list file_name_list; historian.keys(file_name_list); for (j = 0; j < file_name_list.size(); ++j) { nstring file_name = file_name_list[j]; assert(file_name.length()); file_event *fep = historian.get_last(file_name); assert(fep); if (!fep) continue; assert(fep->get_src()); if ( attributes_list_find_boolean ( fep->get_src()->attribute, "entire-source-hide" ) ) { continue; } if ( attributes_list_find_boolean ( fep->get_src()->attribute, "local-source-hide" ) ) { continue; } switch (fep->get_src()->usage) { case file_usage_build: switch (fep->get_src()->action) { case file_action_modify: continue; case file_action_create: case file_action_remove: case file_action_insulate: case file_action_transparent: break; } // fall through... case file_usage_source: case file_usage_config: case file_usage_test: case file_usage_manual_test: switch (fep->get_src()->action) { case file_action_create: case file_action_modify: case file_action_remove: break; case file_action_insulate: case file_action_transparent: // can't happen assert(0); continue; } break; } if (!have_it_already(change_set, fep->get_src())) { if ( !use_config && fep->get_src()->usage == file_usage_config ) { fep->get_src()->usage = file_usage_source; } one_more_src ( historian, change_set, fep->get_src(), use_attributes ); } } } else { trace(("adding project files...\n")); for (j = 0;; ++j) { fstate_src_ty *src_data; src_data = pp->file_nth(j, view_path_simple); if (!src_data) break; if ( attributes_list_find_boolean ( src_data->attribute, "entire-source-hide" ) ) { continue; } if ( attributes_list_find_boolean ( src_data->attribute, "local-source-hide" ) ) { continue; } switch (src_data->usage) { case file_usage_build: switch (src_data->action) { case file_action_modify: continue; case file_action_create: case file_action_remove: case file_action_insulate: case file_action_transparent: break; } // fall through... case file_usage_source: case file_usage_config: case file_usage_test: case file_usage_manual_test: switch (src_data->action) { case file_action_create: case file_action_modify: case file_action_remove: break; case file_action_insulate: case file_action_transparent: // can't happen assert(0); continue; } break; } if (!have_it_already(change_set, src_data)) { if (!use_config && src_data->usage == file_usage_config) src_data->usage = file_usage_source; one_more_src ( historian, change_set, src_data, use_attributes ); } } } } if (!change_set->src || !change_set->src->length) change_fatal(cp, 0, i18n("bad send no files")); // // Adjust the content of the change set to make it contains only // operations that can be performed in a 'change'. // if (entire_source) { // // We collapse multiple rename chains into just one rename // operation: // // A renamed to B renamed to C // // will become // // A renamed to C // trace(("%ld\n",(long)change_set->src->length)); for (size_t i = 0; i < change_set->src->length; ++i) { if (!change_set->src->list[i]) continue; cstate_src_ty *csrc = change_set->src->list[i]; if (!csrc->move) continue; trace(("%s \"%s\" move \"%s\"\n", file_action_ename(csrc->action), csrc->file_name->str_text, csrc->move->str_text)); cstate_src_ty *csrc2 = 0; for (size_t k = i + 1; k < change_set->src->length; ++k) { csrc2 = change_set->src->list[k]; if (!csrc2) continue; if (!csrc2->move) continue; // // We have found a regular rename. // if ( str_equal(csrc->move, csrc2->file_name) && str_equal(csrc2->move, csrc->file_name) ) { trace(("normal move\n")); goto loop_end; } // // We have found two unrelated files. // if ( !str_equal(csrc->move, csrc2->file_name) && !str_equal(csrc2->move, csrc->file_name) ) continue; // // If this is not a simple rename, things get // complicated, because the links (via the "move" field) // only make sense when you are doing a history roll // forward. That is, if a file is moved twice, // you would need *two* move fields to record the // information, and there is only one. // // So we turn the move into a simple create or remove // action. Some work will be needed to avoid trying to // both rename and remove a file. // if (csrc->uuid && csrc->action == file_action_remove) { // // The file has been renamed more than once. We // delete dangling removes because we know there // is a complete rename pair elsewhere in the // list. Any such rename will be done correctly by // consulting the UUID field of the files. // trace(("toss \"%s\" %s\n", csrc->file_name->str_text, file_action_ename(csrc->action))); cstate_src_type.free(csrc); change_set->src->list[i] = 0; } else { trace(("drop remove attribute\n")); str_free(csrc->move); csrc->move = 0; } } loop_end:; } // // Now the change_set may contain holes (0), we need to remove // them before further operations. We are not forced to keep // the order because of the next qsort invocation. // for (size_t k = 0; k < change_set->src->length; ++k) { trace(("k = %ld of %ld\n", (long)k, (long)change_set->src->length)); if (change_set->src->list[k]) continue; while (!change_set->src->list[--change_set->src->length]) ; change_set->src->list[k] = change_set->src->list[change_set->src->length]; } #ifdef DEBUG for (size_t k = 0; k < change_set->src->length; ++k) { assert(change_set->src->list[k]); } #endif } // // sort the files by name // qsort ( change_set->src->list, change_set->src->length, sizeof(change_set->src->list[0]), cmp ); os_become_orig(); childs_name = "etc/change-set"; ofp = cpio_p->child(childs_name, -1); ofp = output_indent::create(ofp); cstate_write(ofp, change_set); ofp.reset(); os_become_undo(); // // We need a whole bunch of temporary files. // diff_output_filename = os_edit_filename(0); os_become_orig(); undo_unlink_errok(diff_output_filename); os_become_undo(); dev_null = str_from_c("/dev/null"); // // add each of the relevant source files to the archive // for (j = 0; j < change_set->src->length; ++j) { cstate_src_ty *csrc; long len; string_ty *original_filename = 0; string_ty *input_filename = 0; int original_filename_unlink = 0; int input_filename_unlink = 0; csrc = change_set->src->list[j]; switch (csrc->usage) { case file_usage_build: continue; case file_usage_source: case file_usage_config: case file_usage_test: case file_usage_manual_test: break; } switch (csrc->action) { case file_action_remove: continue; case file_action_create: case file_action_modify: case file_action_insulate: case file_action_transparent: break; } trace(("file name = \"%s\", action = %s, usage = %s\n", csrc->file_name->str_text, file_action_ename(csrc->action), file_usage_ename(csrc->usage))); if (csrc->move) { trace(("move = \"%s\"\n", csrc->move->str_text)); } // // Find a source file. Depending on the change state, // it could be in the development directory, or in the // baseline or in history. // // original_filename // The oldest version of the file. // input_filename // The youngest version of the file. // diff_output_filename // Where to write the output. // // These names are taken from the substitutions for // the diff_command. It's historical. // switch (cstate_data->state) { case cstate_state_awaiting_development: assert(0); continue; case cstate_state_being_developed: case cstate_state_awaiting_review: case cstate_state_being_reviewed: case cstate_state_awaiting_integration: case cstate_state_being_integrated: // // Get the orginal file. // trace(("%s %s \"%s\"\n", file_usage_ename(csrc->usage), file_action_ename(csrc->action), csrc->file_name->str_text)); switch (csrc->action) { case file_action_create: // // We include the need for a UUID because otherwise the // chain of renames can be an arbitrarily long one (and // could possibly loop). If we only deal with UUIDs, we // simplify the problem immensely. // if (use_rename_patch && csrc->move && csrc->uuid) { if (historian.is_set()) { trace(("using historian, by uuid\n")); file_event_list::pointer orig_felp = historian.get(csrc); // // It's tempting to say // assert(felp); // but file file may not yet exist at this point in // time, so there is no need (or ability) to create a // patch for it. // if (!orig_felp || orig_felp->size() < 2) { original_filename = str_copy(dev_null); trace(("original filename /dev/null\n")); break; } file_event *orig_fep = orig_felp->back(); fstate_src_ty *orig_src = change_file_find ( orig_fep->get_change(), csrc->move, view_path_first ); assert(orig_src); original_filename = project_file_version_path ( pp, orig_src, &original_filename_unlink ); } else { fstate_src_ty *orig_src = project_file_find(pp, csrc, view_path_extreme); if (!orig_src) { original_filename = str_copy(dev_null); trace(("original filename /dev/null\n")); break; } original_filename = project_file_version_path ( pp, orig_src, &original_filename_unlink ); } } else { original_filename = str_copy(dev_null); trace(("original filename /dev/null\n")); } break; case file_action_modify: case file_action_remove: case file_action_insulate: case file_action_transparent: #ifndef DEBUG default: #endif original_filename = project_file_path(pp, csrc->file_name); break; } assert(original_filename); // // Get the input file. // switch (csrc->action) { case file_action_remove: input_filename = str_copy(dev_null); trace(("input filename /dev/null\n")); break; case file_action_transparent: // FIXME: this is wrong, need version from grandparent // fall through... case file_action_create: case file_action_modify: case file_action_insulate: #ifndef DEBUG default: #endif input_filename = change_file_path(cp, csrc->file_name); if (!input_filename) input_filename = project_file_path(pp, csrc->file_name); break; } assert(input_filename); break; case cstate_state_completed: // // Both the versions to be diffed come out // of history. // trace(("%s %s \"%s\"\n", file_usage_ename(csrc->usage), file_action_ename(csrc->action), csrc->file_name->str_text)); switch (csrc->action) { // file_event_list::pointer felp; // file_event *fep; // fstate_src_ty *old_src; case file_action_create: { assert(historian.is_set()); file_event_list::pointer felp = historian.get(csrc); // // It's tempting to say // assert(felp); // but file file may not yet exist at this point in // time, so there is no need (or ability) to create a // patch for it. // if (!felp) { original_filename = str_copy(dev_null); trace(("original filename /dev/null\n")); input_filename = str_copy(dev_null); trace(("input filename /dev/null\n")); break; } assert(!felp->empty()); // // Get the orginal file. We handle the creation half // of a file rename. // // We include the need for a uuid because otherwise the // rename chain can be an arbitrarily long one (and // could possibly loop). If we only deal with UUIDs, we // simplify the problem immensely. // if ( use_rename_patch && csrc->move && csrc->uuid && felp->size() >= 2 ) { // // Do the same as modify. // file_event *fep = felp->get(felp->size() - 2); fstate_src_ty *old_src = change_file_find ( fep->get_change(), csrc, view_path_first ); assert(old_src); original_filename = project_file_version_path ( pp, old_src, &original_filename_unlink ); } else original_filename = str_copy(dev_null); // // Get the input file. // file_event *fep = felp->back(); fstate_src_ty *old_src = change_file_find ( fep->get_change(), csrc, view_path_first ); assert(old_src); input_filename = project_file_version_path ( pp, old_src, &input_filename_unlink ); assert(original_filename); assert(input_filename); } break; case file_action_remove: { // // We ignore the remove half or a file rename. // if (use_rename_patch && csrc->move) { input_filename = str_copy(dev_null); trace(("original filename /dev/null\n")); original_filename = str_copy(dev_null); trace(("input filename /dev/null\n")); break; } assert(historian.is_set()); file_event_list::pointer felp = historian.get(csrc); // // It's tempting to say // assert(felp); // but file file may not yet exist at this point in // time, so there is no need (or ability) to create a // patch for it. // // It is also tempting to say // assert(felp->length >= 2); // except that a file which is created and removed in // the same branch, will result in only a remove record // in its parent branch when integrated. // assert(!felp || !felp->empty()); if (!felp || felp->size() < 2) { original_filename = str_copy(dev_null); } else { // // Get the orginal file. // file_event *fep = felp->get(felp->size() - 2); fstate_src_ty *old_src = change_file_find ( fep->get_change(), csrc->file_name, view_path_first ); assert(old_src); original_filename = project_file_version_path ( pp, old_src, &original_filename_unlink ); } // // Get the input file. // input_filename = str_copy(dev_null); trace(("input filename /dev/null\n")); } break; case file_action_modify: { assert(historian.is_set()); file_event_list::pointer felp = historian.get(csrc); // // It's tempting to say // assert(felp); // but file file may not yet exist at this point in // time, so there is no need (or ability) to create a // patch for it. // assert(!felp || !felp->empty()); if (!felp) { original_filename = str_copy(dev_null); trace(("original filename /dev/null\n")); input_filename = str_copy(dev_null); trace(("input filename /dev/null\n")); break; } // // Get the orginal file. // if (felp->size() < 2) { original_filename = str_copy(dev_null); } else { file_event *fep = felp->get(felp->size() - 2); fstate_src_ty *old_src = change_file_find ( fep->get_change(), csrc->file_name, view_path_first ); assert(old_src); original_filename = project_file_version_path ( pp, old_src, &original_filename_unlink ); } // // Get the input file. // file_event *fep = felp->back(); fstate_src_ty *old_src = change_file_find ( fep->get_change(), csrc->file_name, view_path_first ); assert(old_src); input_filename = project_file_version_path ( pp, old_src, &input_filename_unlink ); } break; case file_action_insulate: // this is supposed to be impossible trace(("insulate = \"%s\"\n", csrc->file_name->str_text)); assert(0); original_filename = str_copy(dev_null); trace(("original filename /dev/null\n")); input_filename = str_copy(dev_null); trace(("input filename /dev/null\n")); break; case file_action_transparent: // no file content appears in the output trace(("transparent = \"%s\"\n", csrc->file_name->str_text)); original_filename = str_copy(dev_null); trace(("original filename /dev/null\n")); input_filename = str_copy(dev_null); trace(("input filename /dev/null\n")); break; } assert(original_filename); assert(input_filename); break; } // // If they are both /dev/null don't bother with a patch. // assert(original_filename); assert(input_filename); if ( !str_equal(original_filename, dev_null) || !str_equal(input_filename, dev_null) ) { // // Put a patch into the archive // for modified files. // // We don't bother with a patch for created files, because // we simply include the whole source in the next section. // bool is_a_rename = false; switch (csrc->action) { case file_action_remove: case file_action_transparent: break; case file_action_create: if (!use_rename_patch || !csrc->move) break; is_a_rename = true; // fall through case file_action_modify: case file_action_insulate: if (entire_source) break; if (!use_patch) break; // // Generate the difference file only if the // original_filename is *not* /dev/null. // // If the original_filename is /dev/null then the // patch will be a full insert, causing a problem // duplicating almost completly the content of the // patched file. // assert(original_filename); trace(("original_filename = \"%s\"\n", original_filename->str_text)); assert(input_filename); trace(("input_filename = \"%s\"\n", input_filename->str_text)); assert(diff_output_filename); trace(("diff_output_filename = \"%s\"\n", diff_output_filename->str_text)); if (str_equal(original_filename, dev_null)) break; change_run_patch_diff_command ( cp, up, original_filename, input_filename, diff_output_filename, csrc->file_name ); // // Read the diff into the archive. // os_become_orig(); input ifp = input_file_open(diff_output_filename, true); assert(ifp.is_open()); len = ifp->length(); assert(len >= 0); if (len > 0 || is_a_rename) { childs_name = nstring::format("patch/%s", csrc->file_name->str_text); output::pointer op = cpio_p->child(childs_name, len); op << ifp; } ifp.close(); os_become_undo(); // It's tempting to say: // // str_free(diff_output_filename); // // but this must really be freed once: out of the loop. break; } } // // Put the whole file into the archive, // for creates and modifies. // // Even though a patch is preferable, sometimes the // destination has never heard of the file, so we include // the whole file just in case. // switch (csrc->action) { case file_action_remove: case file_action_transparent: break; case file_action_create: case file_action_modify: case file_action_insulate: os_become_orig(); input ifp = input_file_open(input_filename); assert(ifp.is_open()); len = ifp->length(); childs_name = nstring::format("src/%s", csrc->file_name->str_text); ofp = cpio_p->child(childs_name, len); ofp << ifp; ifp.close(); ofp.reset(); os_become_undo(); break; } // // Free a bunch of strings. // os_become_orig(); if (original_filename_unlink) os_unlink_errok(original_filename); if (input_filename_unlink) os_unlink_errok(input_filename); os_become_undo(); str_free(original_filename); str_free(input_filename); } cstate_type.free(change_set); // // Get rid of all the temporary files. // os_become_orig(); assert(diff_output_filename); os_unlink_errok(diff_output_filename); os_become_undo(); str_free(diff_output_filename); str_free(dev_null); // finish writing the cpio archive os_become_orig(); delete cpio_p; os_become_undo(); // clean up and go home change_free(cp); project_free(pp); }