//
// aegis - project change supervisor
// Copyright (C) 2001-2008 Peter Miller
// Copyright (C) 2006 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
#include
#include
#include
#include
#include
#include
project_file_roll_forward::~project_file_roll_forward()
{
trace(("project_file_roll_forward::~project_file_roll_forward()\n"));
}
static int
possibly_broken_by_aeimport(change::pointer cp)
{
fstate_src_ty *src;
cstate_ty *cstate_data;
cstate_history_ty *chp;
//
// Older versions of aeimport had a bug, and the very
// first change on the imported branch had an honest time
// stamp, the date of the aeimport run, but it confuses
// this function mightily. (It no longer does this, it
// makes up a timestamp earlier than the first imported
// change set.) As a work around, ignore the limit for
// the first change on such a branch.
//
if (change_file_nth(cp, 1, view_path_first))
return 0;
src = change_file_nth(cp, 0, view_path_first);
if (!src)
return 0;
switch (src->action)
{
case file_action_create:
if (src->usage != file_usage_config)
return 0;
break;
case file_action_modify:
case file_action_remove:
case file_action_insulate:
case file_action_transparent:
#ifndef DEBUG
default:
#endif
return 0;
}
//
// Check that the history is the shape we expect.
//
cstate_data = cp->cstate_get();
if(!cstate_data->history)
return 0;
if (cstate_data->history->length < 4)
return 0;
chp = cstate_data->history->list[cstate_data->history->length - 1];
if (chp->what != cstate_history_what_integrate_pass)
return 0;
if (chp->when - cstate_data->history->list[0]->when >= 10)
return 0;
//
// Tickle the time, so that it is marginally valid.
//
// This won't corrupt anything, because this change will not be
// written out.
//
chp->when = 24*60*60;
return 1;
}
static change_list_ty walk_these_branches;
static change_list_ty *
change_list_get(project_ty *pp, time_t limit)
{
change::pointer pcp;
cstate_ty *cstate_data;
cstate_branch_ty *bp;
size_t j;
change_list_ty *clp;
trace(("change_list_get(pp = %08lX, limit = %ld \"%.24s\")\n{\n",
(long)pp, (long)limit, ctime(&limit)));
trace(("project \"%s\"\n", project_name_get(pp)->str_text));
clp = new change_list_ty();
pcp = pp->change_get();
cstate_data = pcp->cstate_get();
bp = cstate_data->branch;
assert(bp);
if (!bp)
{
trace(("}\n"));
return clp;
}
//
// Work our way down the branch, squirreling away each child
// change as we see it.
//
if (bp->history)
{
for (j = 0; j < bp->history->length; ++j)
{
long change_number;
change::pointer cp;
cstate_branch_history_ty *hp;
cstate_history_ty *chp;
hp = bp->history->list[j];
assert(hp);
change_number = hp->change_number;
cp = change_alloc(pp, change_number);
trace(("cp = %08lX;\n", (long)cp));
change_bind_existing(cp);
cstate_data = cp->cstate_get();
//
// If the change hasn't been completed yet, don't put
// it into the history. It isn't relevant yet.
//
// We are walking the *history* list.
// All these changes are supposed to be completed.
//
assert(cstate_data->state == cstate_state_completed);
if (cstate_data->state != cstate_state_completed)
{
change_free(cp);
continue;
}
//
// If the change was completed after the time limit,
// ignore the change. Also, any later changes are
// irrelevant, too.
//
assert(cstate_data->history);
assert(cstate_data->history->length > 1);
if (!cstate_data->history || cstate_data->history->length < 1)
{
change_free(cp);
continue;
}
chp = cstate_data->history->list[cstate_data->history->length - 1];
trace(("when = %ld \"%.24s\"\n", (long)chp->when,
ctime(&chp->when)));
assert(chp);
//
// Older versions of aeimport had a bug, and the very
// first change on the imported branch had an honest time
// stamp, the date of the aeimport run, but it confuses
// this function mightily. (It no longer does this, it
// makes up a timestamp earlier than the first imported
// change set.) As a work around, ignore the limit for
// the first change on such a branch.
//
if
(
j != 0
||
change_number != 10
||
!possibly_broken_by_aeimport(cp)
)
{
if (chp->when > limit)
{
trace(("toss this one, and all the rest\n"));
change_free(cp);
break;
}
}
trace(("keep this one\n"));
clp->append(cp);
}
}
//
// Also chase down the exception branch list because there may be
// some active branches that should be walked, and they will *not*
// be on the history list.
//
// They go on the end of the list, because they aren't completed yet.
// When they are eventually completed, they will be at this position
// in the list.
//
for (j = 0; j < walk_these_branches.length; ++j)
{
change::pointer cp;
cp = walk_these_branches.item[j];
trace(("pp = %08lX\n", (long)pp));
trace(("cp->pp = %08lX\n", (long)cp->pp));
trace(("same = %d\n", str_equal(project_name_get(cp->pp),
project_name_get(pp))));
if (str_equal(project_name_get(cp->pp), project_name_get(pp)))
clp->append(cp);
}
trace(("return %08lX;\n", (long)clp));
trace(("}\n"));
return clp;
}
struct playback_ty
{
project_ty *pp;
change_list_ty *clp;
size_t position;
size_t pushed;
symtab_ty *files;
};
static void
playback_constructor(playback_ty *pbp, time_t limit, project_ty *pp)
{
trace(("playback_constructor(pbp = %08lX, limit = %ld \"%.24s\", "
"pp = %08lX)\n{\n", (long)pbp, (long)limit, ctime(&limit), (long)pp));
pbp->pp = pp;
pbp->clp = change_list_get(pp, limit);
pbp->position = 0;
pbp->pushed = 0;
pbp->files = new symtab_ty(5);
trace(("}\n"));
}
static void
playback_destructor(playback_ty *pbp)
{
trace(("playback_destructor(pbp = %08lX)\n{\n", (long)pbp));
if (pbp->clp)
delete pbp->clp;
if (pbp->files)
delete pbp->files;
pbp->pp = 0;
pbp->clp = 0;
pbp->position = 0;
pbp->pushed = 0;
pbp->files = 0;
trace(("}\n"));
}
static time_t
playback_when(playback_ty *pbp)
{
if (!pbp || !pbp->clp)
return time_max();
if (pbp->position >= pbp->clp->length)
{
change::pointer cp = pbp->pp->change_get();
return change_completion_timestamp(cp);
}
return change_completion_timestamp(pbp->clp->item[pbp->position]);
}
struct playback_list_ty
{
size_t length;
size_t maximum;
playback_ty *item;
};
static void
playback_list_constructor(playback_list_ty *pblp)
{
trace(("playback_list_constructor(pblp = %08lX)\n{\n", (long)pblp));
pblp->length = 0;
pblp->maximum = 0;
pblp->item = 0;
trace(("}\n"));
}
static void
playback_list_destructor(playback_list_ty *pblp)
{
trace(("playback_list_destructor(pblp = %08lX)\n{\n", (long)pblp));
if (pblp->item)
{
size_t j;
for (j = 0; j < pblp->length; ++j)
playback_destructor(pblp->item + j);
delete [] pblp->item;
}
pblp->length = 0;
pblp->maximum = 0;
pblp->item = 0;
trace(("}\n"));
}
static void
playback_list_push(playback_list_ty *pblp, time_t limit, project_ty *pp)
{
playback_ty *pbp;
trace(("playback_list_push(pblp = %08lX, limit = %ld \"%.24s\", "
"pp = %08lX)\n{\n", (long)pblp, (long)limit, ctime(&limit), (long)pp));
trace(("project \"%s\"\n", project_name_get(pp)->str_text));
if (pblp->length >= pblp->maximum)
{
pblp->maximum = pblp->maximum * 2 + 8;
playback_ty *new_item = new playback_ty [pblp->maximum];
for (size_t j = 0; j < pblp->length; ++j)
new_item[j] = pblp->item[j];
delete [] pblp->item;
pblp->item = new_item;
}
pbp = pblp->item + pblp->length++;
trace(("pbp = %08lX\n", (long)pbp));
playback_constructor(pbp, limit, pp);
trace(("}\n"));
}
static void
playback_list_pop(playback_list_ty *pblp)
{
trace(("playback_list_pop(pblp = %08lX)\n{\n", (long)pblp));
assert(pblp->length > 0);
if (pblp->length > 0)
{
playback_ty *pbp;
pblp->length--;
pbp = pblp->item + pblp->length;
playback_destructor(pbp);
}
trace(("}\n"));
}
static void
playback_list_recinit(playback_list_ty *pblp, time_t limit, project_ty *pp)
{
trace(("playback_list_recinit(pblp = %08lX, limit = %ld \"%.24s\", "
"pp = %08lX)\n{\n", (long)pblp, (long)limit, ctime(&limit), (long)pp));
while (!pp->is_a_trunk())
{
change::pointer cp;
//
// Completed changes will always be placed on the list by
// change_list_get().
//
// Active branches need to be explicitly added, which is what
// the walk_these_branches list is for.
//
// Completed ancestor branches, however, will have a completion
// time stamp after the time limit, and so they, too, need to
// be added to this list.
//
cp = pp->change_get();
walk_these_branches.append(cp);
pp = pp->parent_get();
}
playback_list_push(pblp, limit, pp);
trace(("}\n"));
}
#if 0
static void
playback_trace_real(const char *file, int line, playback_ty *pbp)
{
change::pointer cp;
time_t when;
trace_where(file, line);
trace_printf("playback %08lX = { ", (long)pbp);
if (pbp->position >= pbp->clp->length)
{
cp = pbp->pp->change_get();
trace_printf("project \"%s\" END\n",
project_name_get(pbp->pp)->str_text);
}
else
{
cp = pbp->clp->item[pbp->position];
trace_printf("project \"%s\", change %ld, delta %ld",
project_name_get(cp->pp)->str_text,
magic_zero_decode(cp->number), change_delta_number_get(cp));
}
when = change_completion_timestamp(cp);
trace_printf(", time %ld = %.24s}\n", (long)when, ctime(&when));
}
#define playback_trace(x) playback_trace_real(__FILE__, __LINE__, (x))
#else
#define playback_trace(x)
#endif
static playback_ty *
playback_list_next(playback_list_ty *pblp, size_t *index_p)
{
playback_ty *pbp;
size_t idx;
size_t idx2;
time_t earliest_time;
//
// Find *any* item on the playback list.
//
trace(("playback_list_next(pblp = %08lX)\n{\n", (long)pblp));
for (idx = 0; ; ++idx)
{
if (idx >= pblp->length)
{
trace(("return NULL;\n"));
trace(("}\n"));
return 0;
}
pbp = pblp->item + idx;
if (pbp->position < pbp->clp->length)
break;
}
earliest_time = playback_when(pbp);
playback_trace(pbp);
//
// Now that we have found one, see if there is another one which is
// earlier than first one we found.
//
assert(pbp);
for (idx2 = idx + 1; idx2 < pblp->length; ++idx2)
{
playback_ty *pbp2;
time_t second_time;
pbp2 = pblp->item + idx2;
if (pbp2->position >= pbp2->clp->length)
continue;
playback_trace(pbp2);
second_time = playback_when(pbp2);
if (second_time <= earliest_time)
{
//
// That "or equals" is important - we want the one the
// closest to the top of the stack.
//
pbp = pbp2;
idx = idx2;
earliest_time = second_time;
}
}
*index_p = idx;
trace(("return %08lX;\n", (long)pbp));
trace(("}\n"));
return pbp;
}
static time_t
branch_start_time(project_ty *pp)
{
change::pointer cp;
cstate_ty *cstate_data;
cstate_history_ty *hp;
cp = pp->change_get();
cstate_data = cp->cstate_get();
if (!cstate_data)
return 1;
if (!cstate_data->history)
return 1;
if (!cstate_data->history->length)
return 1;
hp = cstate_data->history->list[0];
if (!hp)
return 1;
return hp->when;
}
static int
branch_is_completed(project_ty *pp)
{
return pp->change_get()->is_completed();
}
static time_t
branch_finish_time(project_ty *pp)
{
change::pointer cp;
cstate_ty *cstate_data;
cstate_history_ty *hp;
cp = pp->change_get();
cstate_data = cp->cstate_get();
if (!cstate_data)
{
barf:
return time_max();
}
if (!cstate_data->history)
goto barf;
if (!cstate_data->history->length)
goto barf;
hp = cstate_data->history->list[cstate_data->history->length - 1];
if (!hp)
goto barf;
if (hp->what != cstate_history_what_integrate_pass)
goto barf;
return hp->when;
}
static string_ty *
uuid_or_filename(fstate_src_ty *src)
{
assert(src);
assert(src->file_name);
return (src->uuid ? src->uuid : src->file_name);
}
time_t
project_file_roll_forward::recapitulate(project_ty *pp, time_t limit,
int detailed)
{
playback_list_ty stack;
size_t j;
time_t result;
change_list_ty *clp;
time_t other_time;
trace(("recapitulate(pp = %08lX, limit = %ld, detailed = %d)\n{\n",
(long)pp, (long)limit, detailed));
result = 0;
playback_list_constructor(&stack);
playback_list_recinit(&stack, limit, pp);
//
// Check that the time requested makes sense.
//
other_time = branch_start_time(pp);
if (limit < other_time)
{
sub_context_ty *scp;
trace(("branch start %ld = %.24s\n", (long)other_time,
ctime(&other_time)));
scp = sub_context_new();
sub_var_set_time(scp, "Number", limit);
sub_var_set_time(scp, "Number2", other_time);
sub_var_optional(scp, "Number2");
project_error(pp, scp, i18n("time $number before branch"));
sub_context_delete(scp);
}
if (branch_is_completed(pp))
{
other_time = branch_finish_time(pp);
if (limit > other_time)
{
sub_context_ty *scp;
trace(("branch finish %ld = %.24s\n", (long)other_time,
ctime(&other_time)));
scp = sub_context_new();
sub_var_set_time(scp, "Number", limit);
sub_var_set_time(scp, "Number2", other_time);
sub_var_optional(scp, "Number2");
project_error(pp, scp, i18n("time $number after branch"));
sub_context_delete(scp);
}
}
//
// Work our way along all the branches in parallel, squirreling away
// each relevant change as we see it.
//
for (;;)
{
playback_ty *pbp;
change::pointer cp;
time_t pb_when;
//
// If the top of the stack is a branch, and we are a detailed
// report, we need to push the branch onto the stack.
//
if (stack.length)
{
for (;;)
{
pbp = stack.item + stack.length - 1;
clp = pbp->clp;
trace(("%ld/%ld/%ld\n", (long)pbp->position, (long)pbp->pushed,
(long)clp->length));
if (pbp->position >= clp->length)
break;
cp = clp->item[pbp->position];
if (!change_was_a_branch(cp))
break;
if (pbp->position < pbp->pushed)
break;
trace(("project \"%s\", change %ld, delta %ld\n",
project_name_get(cp->pp)->str_text,
magic_zero_decode(cp->number),
change_delta_number_get(cp)));
if
(
(detailed && cp->is_completed())
||
walk_these_branches.member_p(cp)
)
{
project_ty *pp2 = cp->pp->bind_branch(cp);
playback_list_push(&stack, limit, pp2);
}
//
// Advance past this change, otherwise we get stuck
// in an infinite loop, because it keeps reappearing
// on the top of the stack.
//
if (detailed || !cp->is_completed())
pbp->position++;
else
pbp->pushed = pbp->position + 1;
}
}
//
// Look at the stack and see which ancestor has the next event.
//
// We are replaying all of the ancestors in parallel, to simulate
// what really happened.
//
size_t idx = 0;
pbp = playback_list_next(&stack, &idx);
trace(("pbp = %08lX;\n", (long)pbp));
if (!pbp)
break;
playback_trace(pbp);
pb_when = playback_when(pbp);
trace(("when = %ld \"%.24s\"\n", (long)pb_when, ctime(&pb_when)));
if (result < pb_when)
{
result = pb_when;
trace(("result = %ld \"%.24s\"\n", (long)result, ctime(&result)));
}
//
// Now that we know what happened next,
// simulate the change.
//
trace(("pbp->clp = %08lX;\n", (long)pbp->clp));
trace(("pbp->clp->length = %ld;\n", (long)pbp->clp->length));
trace(("pbp->position = %ld;\n", (long)pbp->position));
assert(pbp->position < pbp->clp->length);
cp = pbp->clp->item[pbp->position];
assert(cp);
trace(("project \"%s\", change %ld, delta %ld\n",
project_name_get(cp->pp)->str_text,
magic_zero_decode(cp->number), change_delta_number_get(cp)));
last_change = cp;
//
// For each file in the change, look down the view path and see
// if the effects of the change are visable.
//
trace(("cp = %08lX;\n", (long)cp));
if
(
//
// Changes in walk_these_branches may be outside the
// time limit. Branches we are walking because they are
// ancestor branches of the project we want may also appear
// at this point.
//
change_completion_timestamp(cp) <= limit
&&
(!detailed || !change_was_a_branch(cp))
)
{
for (j = 0; ; ++j)
{
fstate_src_ty *src;
change::pointer vp_cp;
fstate_src_ty *vp_src;
size_t vp_idx;
trace(("j = %d\n", (int)j));
src = change_file_nth(cp, j, view_path_first);
if (!src)
break;
trace(("%s\n", src->file_name->str_text));
//
// Add the file to the branch's state. This is important
// so that "looking down the view path" means something.
//
pbp->files->assign(src->file_name, src);
//
// Now look for the file along the view path.
//
// (Watch out, vp_idx is unsigned, so >= 0 is always true,
// that's why its's value is one more than you expect.)
//
vp_cp = 0;
vp_src = 0;
for (vp_idx = stack.length; vp_idx > 0; --vp_idx)
{
playback_ty *vp_pbp;
vp_pbp = stack.item + vp_idx - 1;
if (vp_pbp->position >= vp_pbp->clp->length)
continue;
vp_cp = vp_pbp->clp->item[vp_pbp->position];
vp_src =
(fstate_src_ty *)vp_pbp->files->query(src->file_name);
//
// This logic MUST be the same as used by
// libaegis/project/file/list_get.c otherwise subtle
// and elusive bugs appear.
//
if (vp_src)
{
int found_a_concrete_file;
found_a_concrete_file = 0;
switch (vp_src->action)
{
case file_action_transparent:
break;
case file_action_remove:
found_a_concrete_file = 1;
break;
case file_action_create:
case file_action_modify:
case file_action_insulate:
#ifndef DEBUG
default:
#endif
// should be file_action_remove
assert(!vp_src->deleted_by);
if (vp_src->deleted_by)
{
found_a_concrete_file = 1;
break;
}
// should be file_action_transparent
assert(!vp_src->about_to_be_created_by);
if (vp_src->about_to_be_created_by)
break;
// should be file_action_transparent
assert(!vp_src->about_to_be_copied_by);
if (vp_src->about_to_be_copied_by)
break;
found_a_concrete_file = 1;
break;
}
if (found_a_concrete_file)
break;
}
vp_cp = 0;
vp_src = 0;
}
assert(vp_src);
if (!vp_src)
continue;
//
// If the file changes appearance, from the along-the-view-path
// perspective, append the change to the list of file events.
//
switch (src->action)
{
case file_action_transparent:
if (idx != stack.length - 1)
continue;
break;
case file_action_create:
case file_action_modify:
case file_action_remove:
case file_action_insulate:
#ifndef DEBUG
default:
#endif
if (src != vp_src)
continue;
break;
}
//
// Locate the file events we are tracking for this file.
// Create one if we haven't seen it before.
//
nstring uuid(uuid_or_filename(vp_src));
file_event_list::pointer felp = uuid_to_felp.get(uuid);
if (!felp)
{
felp = file_event_list::create();
uuid_to_felp.assign(nstring(vp_src->file_name), felp);
trace(("uuid_to_felp[\"%s\"] = %08lX\n",
vp_src->file_name->str_text, (long)felp.get()));
if (vp_src->uuid)
{
uuid_to_felp.assign(nstring(vp_src->uuid), felp);
trace(("uuid_to_felp[\"%s\"] = %08lX\n",
vp_src->uuid->str_text, (long)felp.get()));
}
}
//
// Update the mapping from file name to file UUID.
// (Watch out for the remove half of a rename.)
//
if
(
!vp_src->uuid
||
vp_src->action != file_action_remove
||
!vp_src->move
)
{
filename_to_uuid.assign(vp_src->file_name, uuid.get_ref());
trace(("filename_to_uuid[\"%s\"] = \"%s\"\n",
vp_src->file_name->str_text, uuid.c_str()));
}
//
// Note that we insert vp_src, not src. This is because
// the file entry pointed to by src may be transparent,
// whereas the file entry pointed to by vp_src will
// be concrete.
//
trace(("vp_src = %08lX\n", (long)vp_src));
trace(("%s \"%s\" %s\n",
file_action_ename(vp_src->action),
vp_src->file_name->str_text,
(vp_src->edit && vp_src->edit->revision ?
vp_src->edit->revision->str_text : "")));
if
(
vp_src->move
&&
vp_src->uuid
&&
change_file_find(vp_cp, vp_src->move, view_path_first)
)
{
//
// Note that moved files need special handling,
// because they are modeled as a remove and a
// create. They could come in either order, but
// they will both be in the same change. Ignore the
// remove, but keep the create.
//
switch (vp_src->action)
{
case file_action_remove:
break;
#ifndef DEBUG
default:
#endif
case file_action_create:
case file_action_modify:
case file_action_transparent:
case file_action_insulate:
felp->push_back
(
new file_event(playback_when(pbp), vp_cp, vp_src)
);
break;
}
}
else
{
felp->push_back
(
new file_event(playback_when(pbp), vp_cp, vp_src)
);
}
}
}
//
// Advance this branch one change.
//
trace(("mark\n"));
pbp->position++;
//
// Toss finished branches from the top of the stack.
//
while (stack.length)
{
pbp = stack.item + stack.length - 1;
if (pbp->position < pbp->clp->length)
break;
playback_list_pop(&stack);
}
}
playback_list_destructor(&stack);
trace(("return %ld;\n", (long)result));
trace(("}\n"));
return result;
}
project_file_roll_forward::project_file_roll_forward() :
stp_time(0),
last_change(0)
{
uuid_to_felp.set_reaper();
}
project_file_roll_forward::project_file_roll_forward(project_ty *pp,
time_t limit, int detailed) :
stp_time(0),
last_change(0)
{
set(pp, limit, detailed);
}
void
project_file_roll_forward::set(project_ty *pp, time_t limit, int detailed)
{
trace(("project_file_roll_forward(pp = %08lX, limit = %ld \"%.24s\", "
"detailed = %d)\n{\n", (long)pp, (long)limit, ctime(&limit), detailed));
assert(filename_to_uuid.empty());
assert(uuid_to_felp.empty());
walk_these_branches.clear();
stp_time = recapitulate(pp, limit, !!detailed);
trace(("}\n"));
}
file_event_list::pointer
project_file_roll_forward::get(const nstring &filename)
{
trace(("project_file_roll_forward::get(\"%s\")\n{\n", filename.c_str()));
assert(!filename_to_uuid.empty());
nstring uuid(filename_to_uuid.query(filename));
if (!uuid)
{
trace(("return NULL;\n"));
trace(("}\n"));
return file_event_list::pointer();
}
assert(!uuid_to_felp.empty());
file_event_list::pointer result = uuid_to_felp.get(uuid);
//
// Due to a (now fixed) bug in the aeipass code it is possible for
// the file event list to miss a creation event. We need to detect
// this condition and fix it.
//
if (result)
{
bool file_action_create_missing = true;
for (size_t j = 0; j < result->size(); ++j)
{
file_event *fep = result->get(j);
assert(fep);
fstate_src_ty *fstate_src = fep->get_src();
assert(fstate_src);
if (fstate_src->action == file_action_create)
{
file_action_create_missing = false;
break;
}
}
if (!result->empty() && file_action_create_missing)
{
//
// If the uuid string really contains a UUID we need to look
// for the event list bound to the file_name, because that
// will be where the missing create action was stored.
//
assert(uuid);
if (universal_unique_identifier_valid(uuid))
{
//
// add the by-uuid list to the end of the by-filename list,
// to build the complete event list
//
file_event_list::pointer felp2 =
uuid_to_felp.get(nstring(filename));
assert(felp2);
felp2->push_back(*result);
//
// Make sure the by-uuid and by-filename lookups will
// both be the same from now on.
//
uuid_to_felp.assign(uuid, felp2);
result = uuid_to_felp.get(uuid);
assert(result);
assert(result == felp2);
}
}
#ifdef DEBUG
for (size_t j = 0; j < result->size(); ++j)
{
file_event *fep = result->get(j);
change::pointer cp = fep->get_change();
time_t when = fep->get_when();
trace(("%s, %ld, %.24s\n", project_name_get(cp->pp)->str_text,
cp->number, ctime(&when)));
}
#endif
}
trace(("return %08lX\n", (long)result.get()));
trace(("}\n"));
return result;
}
file_event_list::pointer
project_file_roll_forward::get(string_ty *filename)
{
return get(nstring(filename));
}
file_event_list::pointer
project_file_roll_forward::get(fstate_src_ty *src)
{
trace(("project_file_roll_forward::get(%08lX)\n{\n", (long)src));
trace(("src->file_name = \"%s\";\n", src->file_name->str_text));
nstring uuid(uuid_or_filename(src));
trace(("uuid = %s\n", uuid.quote_c().c_str()));
file_event_list::pointer result = uuid_to_felp.get(uuid);
trace(("result = %08lX;\n", (long)result.get()));
//
// Due to a (now fixed) bug in the aeipass code it is possible for
// the file event list to miss a creation event. We need to detect
// this condition and fix it.
//
if (result)
{
bool file_action_create_missing = true;
for (size_t j = 0; j < result->size(); ++j)
{
file_event *fep = result->get(j);
assert(fep);
fstate_src_ty *fstate_src = fep->get_src();
assert(fstate_src);
if (fstate_src->action == file_action_create)
{
file_action_create_missing = false;
break;
}
}
if (!result->empty() && file_action_create_missing)
{
//
// If the uuid string really contains a UUID we need to look
// for the event list bound to the file_name, because that
// will be where the missing create action was stored.
//
if (universal_unique_identifier_valid(uuid))
{
//
// add the by-uuid list to the end of the by-filename list,
// to build the complete event list
//
file_event_list::pointer felp2 =
uuid_to_felp.get(nstring(src->file_name));
assert(felp2);
felp2->push_back(*result);
//
// make sure the by-uuid and by-filename lookups will
// both be the same from now on.
//
uuid_to_felp.assign(uuid, felp2);
result = uuid_to_felp.get(uuid);
assert(result);
assert(result == felp2);
}
}
#ifdef DEBUG
for (size_t j = 0; j < result->size(); ++j)
{
file_event *fep = result->get(j);
change::pointer cp = fep->get_change();
time_t when = fep->get_when();
trace(("%s, %ld, %.24s\n", project_name_get(cp->pp)->str_text,
cp->number, ctime(&when)));
}
#endif
}
trace(("return %08lX\n", (long)result.get()));
trace(("}\n"));
return result;
}
static string_ty *
uuid_or_filename(cstate_src_ty *src)
{
assert(src);
assert(src->file_name);
return (src->uuid ? src->uuid : src->file_name);
}
file_event_list::pointer
project_file_roll_forward::get(cstate_src_ty *src)
{
trace(("project_file_roll_forward::get(%08lX)\n{\n", (long)src));
trace(("src->file_name = \"%s\";\n", src->file_name->str_text));
nstring uuid(uuid_or_filename(src));
trace(("uuid = %s;\n", uuid.quote_c().c_str()));
assert(!uuid_to_felp.empty());
file_event_list::pointer result = uuid_to_felp.get(uuid);
trace(("result = %08lX;\n", (long)result.get()));
//
// Due to a (now fixed) bug in the aeipass code it is possible for
// the file event list to miss a creation event. We need to detect
// this condition and fix it.
//
if (result)
{
bool file_action_create_missing = true;
for (size_t j = 0; j < result->size(); ++j)
{
file_event *fep = result->get(j);
assert(fep);
fstate_src_ty *fstate_src = fep->get_src();
assert(fstate_src);
if (fstate_src->action == file_action_create)
{
file_action_create_missing = false;
break;
}
}
if (!result->empty() && file_action_create_missing)
{
//
// If the uuid string really contains a UUID we need to look
// for the event list bound to the file_name, because that
// will be where the missing create action was stored.
//
if (universal_unique_identifier_valid(uuid))
{
//
// add the by-uuid list to the end of the by-filename list,
// to build the complete event list
//
file_event_list::pointer felp2 =
uuid_to_felp.get(nstring(src->file_name));
assert(felp2);
felp2->push_back(*result);
//
// make sure the by-uuid and by-filename lookups will
// both be the same from now on.
//
uuid_to_felp.assign(uuid, felp2);
result = uuid_to_felp.get(uuid);
assert(result);
assert(result == felp2);
}
}
#ifdef DEBUG
for (size_t j = 0; j < result->size(); ++j)
{
file_event *fep = result->get(j);
change::pointer cp = fep->get_change();
time_t when = fep->get_when();
trace(("%s, %ld, %.24s\n", project_name_get(cp->pp)->str_text,
cp->number, ctime(&when)));
}
#endif
}
trace(("return %08lX\n", (long)result.get()));
trace(("}\n"));
return result;
}
file_event *
project_file_roll_forward::get_last(const nstring &filename)
{
trace(("project_file_roll_forward::get_last(%s)\n{\n", filename.c_str()));
file_event_list::pointer felp = get(filename);
if (!felp || felp->empty())
{
trace(("return NULL;\n"));
trace(("}\n"));
return 0;
}
file_event *result = felp->back();
trace(("return %08lX (%08lX)\n", (long)result, (long)result->get_src()));
trace(("}\n"));
return result;
}
file_event *
project_file_roll_forward::get_last(string_ty *filename)
{
return get_last(nstring(filename));
}
file_event *
project_file_roll_forward::get_last(fstate_src_ty *src)
{
trace(("project_file_roll_forward::get_last_by_uuid(%s)\n{\n",
src->file_name->str_text));
file_event_list::pointer felp = get(src);
if (!felp || felp->empty())
{
trace(("return NULL;\n"));
trace(("}\n"));
return 0;
}
file_event *result = felp->back();
trace(("return %08lX (%08lX)\n", (long)result, (long)result->get_src()));
trace(("}\n"));
return result;
}
file_event *
project_file_roll_forward::get_older(const nstring &filename)
{
trace(("project_file_roll_forward::get_older(%s)\n{\n", filename.c_str()));
assert(!filename_to_uuid.empty());
nstring uuid(filename_to_uuid.query(filename));
if (!uuid)
{
trace(("return NULL;\n"));
trace(("}\n"));
return 0;
}
file_event_list::pointer felp = uuid_to_felp.get(uuid);
if (!felp || felp->empty())
{
trace(("return NULL;\n"));
trace(("}\n"));
return 0;
}
file_event *result = 0;
for (size_t j = felp->size(); j > 0; --j)
{
//
// We don't simply want the second last entry, we want the
// last entry before the time of the last change. This is
// because we want the previous delta, not the previous
// file version. For some files this will be the last entry,
// and other files this will be the second last entry.
//
file_event *fep = felp->get(j - 1);
time_t when = change_completion_timestamp(fep->get_change());
if (when < stp_time)
{
result = fep;
break;
}
}
trace(("return %08lX (%08lX)\n", (long)result, (long)result->get_src()));
trace(("}\n"));
return result;
}
file_event *
project_file_roll_forward::get_older(string_ty *filename)
{
return get_older(nstring(filename));
}
void
project_file_roll_forward::keys(nstring_list &result)
{
assert(!filename_to_uuid.empty());
filename_to_uuid.keys(result);
result.sort();
}
change::pointer
project_file_roll_forward::get_last_change()
const
{
assert(last_change);
return last_change;
}