//
// aegis - project change supervisor
// Copyright (C) 1999, 2001-2008 Peter Miller
// Copyright (C) 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 // for assert
#include
#include
#include
#include
#include
#include
#include
#include
static void
fimprove(fstate_ty *fstate_data, string_ty *, change::pointer cp)
{
trace(("fimprove(%08lX, , %s)\n{\n", (long)fstate_data,
change_version_get(cp)->str_text));
if (!fstate_data->src)
{
fstate_data->src = (fstate_src_list_ty *)fstate_src_list_type.alloc();
}
//
// Migrate file state information.
//
symtab create_half;
symtab remove_half;
for (size_t j = 0; j < fstate_data->src->length; ++j)
{
fstate_src_ty *src;
src = fstate_data->src->list[j];
//
// Fix an ugly inconsistency in the file action.
//
if (src->deleted_by)
src->action = file_action_remove;
//
// Historical 4.9 -> 4.10 transition.
//
// This is here to cope with cases where the users upgrade with
// changes between 'being reviewed' and 'being integrated' states.
//
// This code must agree with the corresponding code in
// libaegis/project.c
//
switch (src->action)
{
case file_action_transparent:
break;
case file_action_remove:
//
// There was once a bug in aeclone which caused the
// edit_origin field to be omitted for removed files. This,
// in turn, led to project file entries with no edit_origin
// field, which hauses assert failues and segfaults all over
// the place.
//
if (!src->edit_origin && !src->edit_number_origin)
{
//
// The problem is , what the heck to we replace it with?
// Probably not important, since it will never be passed
// to the history_get_command.
//
src->edit_origin =
(history_version_ty *)history_version_type.alloc();
src->edit_origin->errpos = str_copy(src->errpos);
src->edit_origin->revision = str_from_c("0.0");
src->edit_origin->encoding = history_version_encoding_none;
}
break;
case file_action_create:
case file_action_modify:
case file_action_insulate:
#ifndef DEBUG
default:
#endif
if (src->about_to_be_created_by || src->about_to_be_copied_by)
src->action = file_action_transparent;
break;
}
//
// Historical 2.3 -> 3.0 transition.
//
// This covers a transitional glitch in the edit number
// semantics. Very few installed sites will ever
// need this.
//
if (src->edit_number && !src->edit_number_origin)
src->edit_number_origin = str_copy(src->edit_number);
//
// Historical 3.24 to 3.25 transition.
//
// This was when history file contents encoding was added,
// so that we could cope with binary files transparently,
// even for ascii-only history tools.
//
if (src->edit_number)
{
src->edit = (history_version_ty *)history_version_type.alloc();
src->edit->errpos = str_copy(src->errpos);
src->edit->revision = src->edit_number;
src->edit->encoding = history_version_encoding_none;
src->edit_number = 0;
}
if (src->edit_number_origin)
{
src->edit_origin =
(history_version_ty *)history_version_type.alloc();
src->edit_origin->errpos = str_copy(src->errpos);
src->edit_origin->revision = src->edit_number_origin;
src->edit_origin->encoding = history_version_encoding_none;
src->edit_number_origin = 0;
}
if (src->edit_number_origin_new)
{
src->edit_origin_new =
(history_version_ty *)history_version_type.alloc();
src->edit_origin_new->errpos = str_copy(src->errpos);
src->edit_origin_new->revision = src->edit_number_origin_new;
src->edit_origin_new->encoding = history_version_encoding_none;
src->edit_number_origin_new = 0;
}
//
// Make sure things are where they are meant to be.
//
if (src->edit && !src->edit->revision)
{
sub_context_ty *scp;
assert(src->edit->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", src->edit->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "edit.revision");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (src->edit_origin && !src->edit_origin->revision)
{
sub_context_ty *scp;
assert(src->edit_origin->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", src->edit_origin->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "edit_origin.revision");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
if (src->edit_origin_new && !src->edit_origin_new->revision)
{
sub_context_ty *scp;
assert(src->edit_origin_new->errpos);
scp = sub_context_new();
sub_var_set_string(scp, "File_Name", src->edit_origin_new->errpos);
sub_var_set_charstar(scp, "FieLD_Name", "edit_origin_new.revision");
change_fatal
(
cp,
scp,
i18n("$filename: contains no \"$field_name\" field")
);
// NOTREACHED
sub_context_delete(scp);
}
//
// Adjust the move field in order to cope with a bug in the
// aeipass code. We need another pass to fix the removed
// files.
//
switch (src->action)
{
case file_action_remove:
if (src->move && change_was_a_branch(cp))
{
trace(("remember %s;\n", src->file_name->str_text));
remove_half.assign (nstring(src->file_name), src);
}
break;
case file_action_create:
if (src->move && change_was_a_branch(cp))
create_half.assign(nstring(src->file_name), src);
break;
case file_action_insulate:
case file_action_modify:
case file_action_transparent:
#ifndef DEBUG
default:
#endif
if (src->move)
{
trace(("reset move;\n"));
str_free(src->move);
src->move = 0;
}
break;
}
}
//
// Adjust the move field, we now fix the deleted files.
//
if (remove_half.size() > 0)
{
assert(change_was_a_branch(cp));
nstring_list candidate;
remove_half.keys(candidate);
trace(("candidate.size() = %ld;\n", (long)candidate.size()));
for (size_t i = 0; i < remove_half.size(); ++i)
{
trace_nstring(candidate[i]);
fstate_src_ty *src2 = remove_half.query(candidate[i]);
//
// Skip over if src2 is NULL, it means the file has already
// been removed from the list.
//
if (!src2)
continue;
assert(src2->move);
trace_string(src2->move->str_text);
fstate_src_ty *src3 = remove_half.query(src2->move);
trace_pointer(src3);
//
// Skip over is src3 is NULL, it means the file has already
// been removed from the list.
//
if (!src3)
continue;
assert(src3->move);
//
// Skip over if src3->move is the create half of a rename.
//
if (create_half.query(src3->move))
continue;
//
// If the deletion comes from the same branch we need to go
// down one step to retrieve the right value.
// Otherwise we compare the integration_timestamp of the changes.
//
project_ty *pp = cp->pp->bind_branch(cp);
assert(pp);
trace(("pp->version = \"%s\";\n",
project_version_get(pp)->str_text));
if (src2->deleted_by == src3->deleted_by)
{
change::pointer cp2 = change_alloc(pp, src2->deleted_by);
change_bind_existing(cp2);
change_fstate_get(cp2);
assert(cp2->fstate_stp);
fstate_src_ty *src2_tmp =
(fstate_src_ty*)cp2->fstate_stp->query(src2->file_name);
fstate_src_ty *src3_tmp =
(fstate_src_ty*)cp2->fstate_stp->query(src3->file_name);
assert
(
(src3_tmp->move && !src2_tmp->move)
||
(src2_tmp->move && !src3_tmp->move)
);
if (!src2_tmp->move)
{
str_free(src2->move);
src2->move = 0;
remove_half.remove(nstring(src2->file_name));
}
else
{
assert(str_equal(src2->move, src2_tmp->move));
}
if (!src3_tmp->move)
{
str_free(src3->move);
src3->move = 0;
remove_half.remove(nstring(src3->file_name));
}
else
{
assert(str_equal(src3->move, src3_tmp->move));
}
}
else
{
//
// The files are modified by two *different* changes.
// Since the only possibile scenario is to have a
// rename followed by a delete (and not the other
// way), the most recent change is a deletion and
// it cannot have the move field set.
//
time_t completion_timestamp2 =
pp->change_completion_timestamp(src2->deleted_by);
time_t completion_timestamp3 =
pp->change_completion_timestamp(src3->deleted_by);
assert(completion_timestamp2 != completion_timestamp3);
if (completion_timestamp2 < completion_timestamp3)
{
trace(("reset move;\n"));
str_free(src3->move);
src3->move = 0;
remove_half.remove(nstring(src3->file_name));
}
else
{
trace(("reset move;\n"));
str_free(src2->move);
src2->move = 0;
remove_half.remove(candidate[i]);
}
}
// project_free(pp);
}
}
trace(("return;\n"));
trace(("}\n"));
}
fstate_ty *
change_fstate_get(change::pointer cp)
{
string_ty *fn;
size_t j;
//
// make sure the change state has been read in,
// in case its src field needed to be converted.
// (also to ensure lock_sync has been called for both)
//
trace(("change_fstate_get(cp = %08lX)\n{\n", (long)cp));
cp->cstate_get();
if (!cp->fstate_data)
{
fn = change_fstate_filename_get(cp);
change_become(cp);
cp->fstate_data = fstate_read_file(fn);
change_become_undo(cp);
fimprove(cp->fstate_data, fn, cp);
}
if (!cp->fstate_data->src)
{
cp->fstate_data->src =
(fstate_src_list_ty *)fstate_src_list_type.alloc();
}
//
// Create a couple of O(1) indexes.
// This speeds up just about everything.
//
if (!cp->fstate_stp)
{
cp->fstate_stp = symtab_alloc(cp->fstate_data->src->length);
cp->fstate_uuid_stp = symtab_alloc(cp->fstate_data->src->length);
for (j = 0; j < cp->fstate_data->src->length; ++j)
{
fstate_src_ty *p = cp->fstate_data->src->list[j];
symtab_assign(cp->fstate_stp, p->file_name, p);
if (p->uuid && (!p->move || p->action != file_action_remove))
symtab_assign(cp->fstate_uuid_stp, p->uuid, p);
}
}
trace(("return %08lX;\n", (long)cp->fstate_data));
trace(("}\n"));
return cp->fstate_data;
}