//
// aegis - project change supervisor
// Copyright (C) 2005-2008 Peter Miller
//
// 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
#define NO_TIME_SET ((time_t)(-1))
static bool
have_it_already(fstate_src_list_ty *file_list, fstate_src_ty *src_data)
{
assert(file_list);
assert(src_data);
for (size_t j = 0; j < file_list->length; ++j)
{
fstate_src_ty *dst_data = file_list->list[j];
assert(dst_data);
if (str_equal(dst_data->file_name, src_data->file_name))
return true;
}
return false;
}
static void
one_more_src(fstate_src_list_ty *file_list, fstate_src_ty *src_data)
{
trace(("add \"%s\"\n", src_data->file_name->str_text));
if (src_data->action == file_action_remove && src_data->move)
{
// Ignore the remove half of a move because we will use the
// "move" action for the create half.
return;
}
assert(file_list);
meta_type *type_p = 0;
fstate_src_ty **dst_data_p =
(fstate_src_ty **)fstate_src_list_type.list_parse(file_list, &type_p);
assert(type_p == &fstate_src_type);
fstate_src_ty *dst_data = (fstate_src_ty *)fstate_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 (src_data->attribute)
dst_data->attribute = attributes_list_copy(src_data->attribute);
if (src_data->uuid)
dst_data->uuid = str_copy(src_data->uuid);
}
static void
one_more_src_unique(fstate_src_list_ty *file_list, fstate_src_ty *src_data)
{
if (!have_it_already(file_list, src_data))
one_more_src(file_list, src_data);
}
static int
cmp(const void *va, const void *vb)
{
const fstate_src_ty *a = *(const fstate_src_ty **)va;
const fstate_src_ty *b = *(const fstate_src_ty **)vb;
return strcasecmp(a->file_name->str_text, b->file_name->str_text);
}
static int
len_printable(string_ty *s, int len_max)
{
if (!s)
return 0;
const char *cp = 0;
// Intentionally the C locale, not the user's locale
for (cp = s->str_text; *cp && isprint((unsigned char)*cp); ++cp)
;
int result = (cp - s->str_text);
if (result > len_max)
result = len_max;
return result;
}
static void
output_attribute(output::pointer ofp, const nstring &name, const nstring &value)
{
ofp->fputs("");
ofp->fputs(name.html_quote());
ofp->fputs("\n");
ofp->fputs(value.html_quote());
ofp->fputs("\n");
}
static void
output_attribute_extn(output::pointer ofp, const nstring &name,
const nstring &value)
{
output_attribute(ofp, "X-Aegis-" + name, value);
}
static void
output_attribute_extn_bool(output::pointer ofp, const nstring &name, bool value)
{
output_attribute_extn(ofp, name, boolean_ename(value));
}
static void
output_attribute_user(output::pointer ofp, const nstring &name,
const nstring &value)
{
output_attribute(ofp, "User-" + name, value);
}
static bool
is_x_attr(const nstring &name)
{
return
(
(name[0] == 'x' || name[0] == 'X')
&&
name[1] == '-'
&&
strchr(name.c_str() + 2, '-')
);
}
static void
output_attribute_list(output::pointer ofp, attributes_list_ty *alp)
{
if (!alp)
return;
for (size_t j = 0; j < alp->length; ++j)
{
attributes_ty *ap = alp->list[j];
assert(ap);
assert(ap->name);
nstring name(ap->name);
assert(ap->value);
nstring value(ap->value);
if (is_x_attr(name))
output_attribute(ofp, name, value);
else
output_attribute_user(ofp, nstring(ap->name), nstring(ap->value));
}
}
static void
emit_time(output::pointer op, const char *name, time_t when)
{
// From the RevML DTD:
// ISO-8601 format in GMT/UCT0
// 2000-12-31 23:59:59Z
struct tm *when_tm = gmtime(&when);
char buffer[100];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S Z", when_tm);
op->fprintf("<%s>%s%s>\n", name, buffer, name);
}
class aerevml_bad_state:
public change_functor
{
public:
aerevml_bad_state() :
change_functor(true)
{
}
void
operator()(change::pointer cp)
{
change_fatal(cp, 0, i18n("bad patch send state"));
}
};
static aerevml_bad_state barf_adev;
static void
element(output::pointer ofp, const char *name, const nstring &value)
{
ofp->fputc('<');
ofp->fputs(name);
ofp->fputc('>');
ofp->fputs(value.html_quote());
ofp->fputs("");
ofp->fputs(name);
ofp->fputs(">\n");
}
static void
element(output::pointer ofp, const char *name, string_ty *value)
{
element(ofp, name, nstring(value));
}
void
revml_send(void)
{
change_identifier cid;
const char *compatibility = 0;
nstring output_filename;
int description_header = -1;
int entire_source = -1;
content_encoding_t ascii_armor = content_encoding_unset;
compression_algorithm_t needs_compression = compression_algorithm_not_set;
int mime_header = -1;
arglex();
while (arglex_token != arglex_token_eoln)
{
switch (arglex_token)
{
default:
generic_argument(usage);
continue;
case arglex_token_baseline:
case arglex_token_branch:
case arglex_token_change:
case arglex_token_delta:
case arglex_token_delta_date:
case arglex_token_delta_from_change:
case arglex_token_grandparent:
case arglex_token_number:
case arglex_token_project:
case arglex_token_trunk:
cid.command_line_parse(usage);
continue;
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_output:
if (!output_filename.empty())
duplicate_option(usage);
switch (arglex())
{
default:
option_needs_file(arglex_token_output, usage);
// NOTREACHED
case arglex_token_stdio:
output_filename = "-";
break;
case arglex_token_string:
output_filename = 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_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_mime_header:
if (mime_header > 0)
duplicate_option(usage);
else if (mime_header >= 0)
{
mime_header_yuck:
mutually_exclusive_options
(
arglex_token_mime_header,
arglex_token_mime_header_not,
usage
);
}
mime_header = 1;
break;
case arglex_token_mime_header_not:
if (mime_header == 0)
duplicate_option(usage);
else if (mime_header >= 0)
goto mime_header_yuck;
mime_header = 0;
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;
}
arglex();
}
cid.command_line_check(usage);
if (entire_source < 0)
entire_source = cid.get_baseline();
bool use_patch = true;
if (entire_source)
use_patch = false;
bool use_bzip2 = true;
if (compatibility)
{
// The bzip2 output was introduced in Peter's 4.21.D184,
// and publicly in the 4.22 release.
use_bzip2 = (strverscmp(compatibility, "4.22") >= 0);
}
//
// 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(cid.get_cp(), cid.get_up());
//
// Open the output
//
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:
break;
case compression_algorithm_gzip:
use_bzip2 = false;
break;
case compression_algorithm_bzip2:
use_bzip2 = true;
break;
}
if (mime_header < 0)
{
//
// Decide whether or not to include a MIME header
// (usually useful when called by aeget).
//
mime_header = (ascii_armor != content_encoding_none);
}
os_become_orig();
output::pointer ofp;
if
(
ascii_armor == content_encoding_none
&&
needs_compression != compression_algorithm_none
)
ofp = output_file::binary_open(output_filename);
else
ofp = output_file::text_open(output_filename);
assert(ofp);
os_become_undo();
if (mime_header)
{
ofp->fputs("MIME-Version: 1.0\n");
ofp->fputs("Content-Type: application/revml\n");
content_encoding_header(ofp, ascii_armor);
string_ty *s = project_name_get(cid.get_pp());
string_ty *s2 = change_brief_description_get(cid.get_cp());
if (entire_source)
s2 = project_description_get(cid.get_pp());
ofp->fprintf
(
"Subject: %.*s - %.*s\n",
len_printable(s, 40),
s->str_text,
len_printable(s2, 80),
s2->str_text
);
if (!cid.get_baseline() && !entire_source)
{
ofp->fprintf
(
"Content-Name: %s.C%3.3ld.revml\n",
project_name_get(cid.get_pp())->str_text,
cid.get_change_number()
);
ofp->fprintf
(
"Content-Disposition: attachment; "
"filename=%s.C%3.3ld.revml\n",
project_name_get(cid.get_pp())->str_text,
cid.get_change_number()
);
}
else
{
ofp->fprintf
(
"Content-Name: %s.revml\n",
project_name_get(cid.get_pp())->str_text
);
ofp->fprintf
(
"Content-Disposition: attachment; filename=%s.revml\n",
project_name_get(cid.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;
}
//
// Emit the doctype and initial wrapper element.
// (We'll need to track the version carefully.)
//
ofp->fputs("\n");
nstring REVML_VERSION = "0.35";
ofp->fputs("fputs(REVML_VERSION);
ofp->fputs("//EN\" \"revml.dtd\">\nfputs(REVML_VERSION);
ofp->fputs("\">\n");
//
// The DTD is quite strict about the order of elements.
//
emit_time(ofp, "time", change_completion_timestamp(cid.get_cp()));
element(ofp, "rep_type", "Aegis");
//
// From the RevML DTD:
//
// "The version number, platform, etc. for the repository. This may
// be needed so that import utilities can figure out what tags mean
// what when a particular repository version changes.
//
// "This is often the output of 'p4 info' or 'cvs -v' + cvs
// environment settings and the cvs -l command."
//
element(ofp, "rep_desc", version_stamp());
//
// Now the element.
//
cstate_ty *cstate_data = cid.get_cp()->cstate_get();
nstring desc;
if (description_header)
{
nstring warning;
if
(
entire_source
?
cstate_data->state < cstate_state_being_integrated
:
cstate_data->state != cstate_state_completed
)
{
warning =
nstring::format
(
"Warning: the original change was in the '%s' state\n",
cstate_state_ename(cstate_data->state)
);
}
time_t when = change_completion_timestamp(cid.get_cp());
desc =
nstring::format
(
"From: %s\nDate: %.24s\n%s\n%s",
cid.get_up()->get_email_address().c_str(),
ctime(&when),
warning.c_str(),
cstate_data->description->str_text
);
}
else if (entire_source)
{
desc = nstring(project_description_get(cid.get_pp()));
}
else
{
desc = nstring(cstate_data->description);
}
element(ofp, "comment", desc);
//
// From the RevML DTD:
//
// "The root of the tree that was extracted to RevML. This is
// usually the source file spec up to (but not including) the first
// component that does not contain a wildcard.
//
// "This comes before branches because it's needed to modify
// branches, specifically the p4_branch_spec's View field. That's a
// theoretical issue as implemented, but it's nice to have things in
// the safest, most mnemonic order, I think."
//
element(ofp, "rev_root", project_name_get(cid.get_pp()));
//
// Add the elements.
//
output_attribute_extn
(
ofp,
"change-number",
nstring::format("%ld", magic_zero_decode(cid.get_change_number()))
);
output_attribute_extn
(
ofp,
"brief-description",
(
entire_source
?
nstring(project_description_get(cid.get_pp()))
:
nstring(cstate_data->brief_description)
)
);
output_attribute_extn(ofp, "description", desc);
output_attribute_extn(ofp, "cause", change_cause_ename(cstate_data->cause));
output_attribute_extn_bool(ofp, "test-exempt", cstate_data->test_exempt);
output_attribute_extn_bool
(
ofp,
"test-baseline-exempt",
cstate_data->test_baseline_exempt
);
output_attribute_extn_bool
(
ofp,
"regression-test-exempt",
cstate_data->regression_test_exempt
);
output_attribute_list(ofp, cstate_data->attribute);
if (!cstate_data->attribute)
{
cstate_data->attribute =
(attributes_list_ty *)attributes_list_type.alloc();
}
change_functor_attribute_list result(false, cstate_data->attribute);
if (change_was_a_branch(cid.get_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(cid.get_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(cid.get_cp());
project_inventory_walk(cid.get_pp(), result, limit);
}
for (size_t an = 0; an < cstate_data->attribute->length; ++an)
{
attributes_ty *ap = cstate_data->attribute->list[an];
output_attribute_user(ofp, nstring(ap->name), nstring(ap->value));
}
if (cstate_data->uuid)
{
output_attribute_extn(ofp, "uuid", nstring(cstate_data->uuid));
}
// architecture
// copyright years
//
// Scan for files to be added to the output.
//
fstate_src_list_ty *file_list =
(fstate_src_list_ty *)fstate_src_list_type.alloc();
for (size_t j = 0;; ++j)
{
fstate_src_ty *src_data =
change_file_nth(cid.get_cp(), j, view_path_first);
if (!src_data)
break;
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(cid.get_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;
}
one_more_src(file_list, src_data);
}
if (entire_source)
{
nstring_list file_name_list;
cid.get_project_file_names(file_name_list);
for (size_t j = 0; j < file_name_list.size(); ++j)
{
fstate_src_ty *src_data = cid.get_project_file(file_name_list[j]);
if
(
attributes_list_find_boolean
(
src_data->attribute,
"entire-source-hide"
)
)
{
continue;
}
one_more_src_unique(file_list, src_data);
}
}
if (file_list->length == 0)
change_fatal(cid.get_cp(), 0, i18n("bad send no files"));
//
// sort the files by name
//
qsort
(
file_list->list,
file_list->length,
sizeof(file_list->list[0]),
cmp
);
#if 0
//
// This was in an old version of the RevML spec.
// It's gone from the 0.35 version.
// It was supposed to allow the display of a progress meter.
//
element(ofp, "file_count", nstring::format("%ld", (long)file_list->length));
#endif
//
// We need a whole bunch of temporary files.
//
nstring diff_output_filename = nstring(os_edit_filename(0));
os_become_orig();
undo_unlink_errok(diff_output_filename);
os_become_undo();
nstring dev_null("/dev/null");
//
// Add each of the relevant source files to the archive
// as elements.
//
for (size_t m = 0; m < file_list->length; ++m)
{
//
// From the RevML DTD:
//
// "Each must have a unique identifier, usually the name
// and revision number, though the precise format depends on
// the source repository. The ID should be derived from the
// metadata in a repeatable fashion."
//
// This raises some questions:
// 1. Unique for each file, or
// 2. Unique for each change set, or
// 3. both?
//
fstate_src_ty *csrc = file_list->list[m];
trace(("file_name = \"%s\"\n", csrc->file_name->str_text));
if (csrc->uuid)
ofp->fprintf("\n", csrc->uuid->str_text);
else
{
nstring s(change_version_get(cid.get_cp()));
ofp->fprintf("\n", s.c_str());
}
//
// Emit the elemrnt.
//
element
(
ofp,
"name",
(
csrc->action == file_action_create && csrc->move
?
csrc->move
:
csrc->file_name
)
);
//
// Emit the element.
//
// Why is this mandatory is a mystery.
//
element
(
ofp,
"source_name",
(
csrc->action == file_action_create && csrc->move
?
csrc->move
:
csrc->file_name
)
);
//
// Emit the element.
//
// There is no comment in the RevML DTD explaining the use of
// the element, or the range of legal values.
//
element(ofp, "source_filebranch_id", "no idea what this means");
//
// Emit the element.
//
// There is no comment in the RevML DTD explaining the use of
// the element, or the range of legal values.
//
element(ofp, "source_repo_id", "no idea what this means");
//
// Find a source file. Depending on the change state,
// it could be in the development directory, or in the
// baseline or in history.
//
// original
// The oldest version of the file.
// input
// 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.
//
file_revision original(dev_null, false);
file_revision input_rev(dev_null, false);
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.
//
if (!entire_source)
{
switch (csrc->action)
{
case file_action_create:
if (csrc->move)
{
project_file_roll_forward *hp = cid.get_historian();
file_event_list::pointer orig_felp =
hp->get(csrc->move);
//
// 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(!orig_felp || !orig_felp->empty());
if (!orig_felp)
break;
file_event *orig_fep = orig_felp->back();
assert(orig_fep);
int temp_unlink = 0;
nstring temp =
nstring
(
project_file_version_path
(
cid.get_pp(),
orig_fep->get_src(),
&temp_unlink
)
);
original = file_revision(temp, temp_unlink);
}
break;
case file_action_modify:
case file_action_remove:
case file_action_insulate:
case file_action_transparent:
#ifndef DEBUG
default:
#endif
original =
file_revision
(
nstring
(
project_file_path(cid.get_pp(), csrc->file_name)
),
false
);
break;
}
}
//
// Get the input file.
//
switch (csrc->action)
{
case file_action_remove:
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
{
string_ty *s =
change_file_path(cid.get_cp(), csrc->file_name);
if (!s)
s = project_file_path(cid.get_pp(), csrc->file_name);
assert(s);
input_rev = file_revision(nstring(s), false);
}
break;
}
break;
case cstate_state_completed:
//
// Both the versions to be diffed come out
// of history.
//
switch (csrc->action)
{
case file_action_create:
{
project_file_roll_forward *hp = cid.get_historian();
file_event_list::pointer felp = hp->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 = file_revision(dev_null, false);
break;
}
assert(!felp->empty());
//
// Get the orginal file. We handle the creation
// half of a file rename.
//
if (csrc->move)
{
file_event_list::pointer orig_felp =
hp->get(csrc->move);
//
// It's tempting to say
// assert(orig_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 think that for every
// remove there must be a corresponding create.
// However if a file is created an removed on a
// branch, and the branch is ended, the parent
// branch only sees the remove.
//
// This means you can have a removed file with a
// history length of exactly one.
//
assert(!orig_felp || !orig_felp->empty());
if (!orig_felp || orig_felp->size() < 2)
{
original = file_revision(dev_null, false);
}
else
{
file_event *orig_fep =
orig_felp->get(orig_felp->size() - 2);
assert(orig_fep);
assert(orig_fep->get_src());
int path_unlink = 0;
nstring path =
nstring
(
project_file_version_path
(
cid.get_pp(),
orig_fep->get_src(),
&path_unlink
)
);
original = file_revision(path, path_unlink);
}
}
else
original = file_revision(dev_null, false);
//
// Get the input file.
//
file_event *fep = felp->back();
assert(fep->get_src());
int path_unlink = 0;
nstring path =
nstring
(
project_file_version_path
(
cid.get_pp(),
fep->get_src(),
&path_unlink
)
);
input_rev = file_revision(path, path_unlink);
}
break;
case file_action_remove:
{
//
// We ignore the remove half of a file rename.
//
if (csrc->move)
{
input_rev = file_revision(dev_null, false);
original = file_revision(dev_null, false);
break;
}
project_file_roll_forward *hp = cid.get_historian();
file_event_list::pointer felp = hp->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 = file_revision(dev_null, false);
}
else
{
//
// Get the orginal file.
//
file_event *fep = felp->get(felp->size() - 2);
assert(fep);
assert(fep->get_src());
int path_unlink = 0;
nstring path =
nstring
(
project_file_version_path
(
cid.get_pp(),
fep->get_src(),
&path_unlink
)
);
original = file_revision(path, path_unlink);
}
//
// Get the input file.
//
input_rev = file_revision(dev_null, false);
}
break;
case file_action_modify:
{
project_file_roll_forward *hp = cid.get_historian();
file_event_list::pointer felp = hp->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 = file_revision(dev_null, false);
input_rev = file_revision(dev_null, false);
break;
}
//
// Get the orginal file.
//
if (felp->size() < 2)
{
original = file_revision(dev_null, false);
}
else
{
file_event *fep = felp->get(felp->size() - 2);
assert(fep);
assert(fep->get_src());
int path_unlink = 0;
nstring path =
nstring
(
project_file_version_path
(
cid.get_pp(),
fep->get_src(),
&path_unlink
)
);
original = file_revision(path, path_unlink);
}
//
// Get the input file.
//
file_event *fep = felp->back();
assert(fep);
assert(fep->get_src());
int path_unlink = 0;
nstring path =
nstring
(
project_file_version_path
(
cid.get_pp(),
fep->get_src(),
&path_unlink
)
);
input_rev = file_revision(path, path_unlink);
}
break;
case file_action_insulate:
// this is supposed to be impossible
trace(("insulate = \"%s\"\n", csrc->file_name->str_text));
assert(0);
original = file_revision(dev_null, false);
input_rev = file_revision(dev_null, false);
break;
case file_action_transparent:
// no file content appears in the output
trace(("transparent = \"%s\"\n", csrc->file_name->str_text));
original = file_revision(dev_null, false);
input_rev = file_revision(dev_null, false);
break;
}
break;
}
//
// Emit the element.
//
switch (csrc->action)
{
case file_action_create:
if (csrc->move)
{
element(ofp, "action", "rename");
element(ofp, "move", csrc->file_name);
}
else
element(ofp, "action", "add");
break;
#ifndef DEBUG
default:
#endif
case file_action_transparent:
case file_action_insulate:
case file_action_modify:
element(ofp, "action", "edit");
break;
case file_action_remove:
element(ofp, "action", "delete");
break;
}
//
// Emit the optional element.
//
// Now that we have the input file, we can see what type it is,
// if the file has no "Content-Type" attribute.
//
attributes_ty *ap =
attributes_list_find(csrc->attribute, "Content-Type");
if (!ap || !ap->value || !ap->value->str_length)
{
os_become_orig();
nstring content_type = os_magic_file(input_rev.get_path());
os_become_undo();
assert(!content_type.empty());
element(ofp, "type", content_type);
}
else
{
element(ofp, "type", ap->value);
}
//
// Emit the element.
//
// Why is this mandatory? And why is there a REV ID attribute
// and also a REV_ID element.
//
// From the RevML DTD:
// "A small integer indicating the revision number of a file
// within the branch."
//
element(ofp, "rev_id", "0");
//
// Emit the element.
//
// There is no comment in the RevML DTD explaining the use of
// the element, or the range of legal values, or why it is
// mandatory.
//
element(ofp, "source_rev_id", "no idea what this means");
//
// Emit the elements.
//
output_attribute_extn(ofp, "usage", file_usage_ename(csrc->usage));
output_attribute_extn(ofp, "action", file_action_ename(csrc->action));
// FIXME: need to look at the change file if the change isn't
// completed yet, because this attribute is set at aede.
output_attribute_extn_bool(ofp, "executable", csrc->executable);
if (!csrc->attribute)
{
csrc->attribute =
(attributes_list_ty *)attributes_list_type.alloc();
}
for (size_t j = 0; j < csrc->attribute->length; ++j)
{
ap = csrc->attribute->list[j];
output_attribute_extn(ofp, nstring(ap->name), nstring(ap->value));
}
if (csrc->uuid)
output_attribute_extn(ofp, "uuid", nstring(csrc->uuid));
//
// Emit the or elements.
//
switch (csrc->action)
{
case file_action_remove:
break;
case file_action_insulate:
case file_action_transparent:
assert(0);
// Fall through...
case file_action_modify:
// FIXME: need to send content for binary files, even when
// we are just editing.
if (!entire_source && input_rev.get_path() != dev_null)
{
//
// Run the patch diff command to form the output.
//
change_run_patch_diff_command
(
cid.get_cp(),
cid.get_up(),
original.get_path().get_ref(),
input_rev.get_path().get_ref(),
diff_output_filename.get_ref(),
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());
if (ifp->length() != 0)
{
// No newline here, or it will add a bogus blank
// line to the start of the delta.
ofp->fprintf("");
output::pointer ofp2 = output_revml_encode::create(ofp);
ofp2 << ifp;
ofp2.reset();
ofp->fputs("\n");
}
ifp.close();
os_become_undo();
break;
}
// fall through...
#ifndef DEBUG
default:
#endif
case file_action_create:
//
// FIXME: need to base64 encode binary files.
//
// Maybe we need to use the same trick as the history put
// command, and use several encodings and send the smallest.
//
os_become_orig();
// No newline here, or it will add a bogus blank line to the
// start of the content.
ofp->fputs("");
input ifp = input_file_open(input_rev.get_path().get_ref());
assert(ifp.is_open());
output::pointer ofp2 = output_revml_encode::create(ofp);
ofp2 << ifp;
ofp2.reset();
ifp.close();
ofp->fputs("\n");
os_become_undo();
break;
}
//
// Emit the optional element.
//
// FIXME: we are supposed to have a here, and it
// probably contains an md5sum of the diff text we just emitted,
// but it isn't described well enough to emit anything. (Before
// or after the base64 encoding?)
//
// End the open element.
//
ofp->fputs("\n");
}
// Do not
// fstate_src_type.free(file_list);
// because this will free everything twice.
file_list = 0;
//
// Get rid of all the temporary files.
//
os_become_orig();
os_unlink_errok(diff_output_filename);
// finish writing the cpio archive
ofp->fputs("\n");
ofp.reset();
os_become_undo();
}