/* -*-Mode: C++;-*-
* PRCS - The Project Revision Control System
* Copyright (C) 1997 Josh MacDonald
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: projdesc.cc 1.9.1.1.1.4.1.17.1.5.1.17.3.9.1.34.2.1 Tue, 05 Feb 2002 07:58:12 -0800 jmacd $
*/
extern "C" {
#include <stdarg.h>
}
#if __GNUG__ < 3
#define pubsync sync
#endif
#include "prcs.h"
#include "projdesc.h"
#include "sexp.h"
#include "fileent.h"
#include "vc.h"
#include "repository.h"
#include "misc.h"
#include "hash.h"
#include "quick.h"
#include "system.h"
#include "checkout.h"
#include "memseg.h"
#include "setkeys.h"
#include "populate.h"
#define prj_lex_val prjtext
#define prj_lex_len prjleng
/* in attrs.cc */
extern const struct AttrDesc*
is_file_attribute (register const char *str, register int len);
static const char bad_match_help_string[] =
"When trying to determine how files are related between versions, PRCS "
"first attempts to find a file with the same file family, then looks for "
"a file with the same name. If both exist, and are not the same file, "
"the match is ambiguous. This can happen if you rename a file and then "
"recreate a file with the original name. You may do this, but it is "
"problematic when performing merge and diff operations";
extern const char *default_merge_descs[14];
extern const MergeAction default_merge_actions[14];
EXTERN int prjleng;
EXTERN const char* prjtext;
EXTERN int prj_lex_this_index;
EXTERN int prj_lex_cur_index;
EXTERN int prj_lineno;
EXTERN int is_builtin_keyword(const char* s, int len);
Dstring* ProjectDescriptor::project_version_name() const { return _project_version_name;}
Dstring* ProjectDescriptor::project_version_major() const { return _project_version_major; }
Dstring* ProjectDescriptor::project_version_minor() const { return _project_version_minor; }
Dstring* ProjectDescriptor::parent_version_name() const { return _parent_version_name; }
Dstring* ProjectDescriptor::parent_version_major() const { return _parent_version_major; }
Dstring* ProjectDescriptor::parent_version_minor() const { return _parent_version_minor; }
Dstring* ProjectDescriptor::project_description() const { return _project_description;}
Dstring* ProjectDescriptor::version_log() const { return _version_log; }
Dstring* ProjectDescriptor::new_version_log() const { return _new_version_log; }
Dstring* ProjectDescriptor::checkin_time() const { return _checkin_time; }
Dstring* ProjectDescriptor::checkin_login() const { return _checkin_login; }
Dstring* ProjectDescriptor::created_by_major() const { return _created_by_major; }
Dstring* ProjectDescriptor::created_by_minor() const { return _created_by_minor; }
Dstring* ProjectDescriptor::created_by_micro() const { return _created_by_micro; }
MergeParentEntryPtrArray* ProjectDescriptor::new_merge_parents() const { return _new_merge_parents; }
void ProjectDescriptor::merge_notify_incomplete() { _merge_incomplete = true; }
void ProjectDescriptor::merge_notify_complete() { _merge_complete = true; }
RepEntry *ProjectDescriptor::repository_entry() const { return _repository_entry; }
void ProjectDescriptor::repository_entry(RepEntry* rep) { _repository_entry = rep; }
QuickElim *ProjectDescriptor::quick_elim() const { return _quick_elim; }
void ProjectDescriptor::quick_elim(QuickElim* qe) { _quick_elim = qe; }
const char *ProjectDescriptor::project_full_name() const { return _project_full_name; }
const char *ProjectDescriptor::project_name() const { return _project_name; }
const char *ProjectDescriptor::project_file_path() const { return _project_file_path; }
const char *ProjectDescriptor::project_aux_path() const { return _project_aux_path; }
const char *ProjectDescriptor::project_path() const { return _project_path; }
const char *ProjectDescriptor::full_version() const { return _full_version; }
OrderedStringTable* ProjectDescriptor::populate_ignore() const { return _populate_ignore; };
ProjectVersionData* ProjectDescriptor::project_version_data() const { return _project_version_data; }
FileEntryPtrArray* ProjectDescriptor::file_entries() { return _file_entries; }
PrettyOstream& ProjectDescriptor::merge_log() const { return *_log; }
void ProjectDescriptor::merge_log(PrettyOstream& str) { _log = &str; }
ProjectDescriptor::~ProjectDescriptor()
{
if (_segment) delete (_segment);
if (_file_entries) {
foreach_fileent (fe_ptr, this)
delete (*fe_ptr);
delete (_file_entries);
}
if (_log_pstream) {
if (_log_pstream->pubsync()) {
prcserror << "warning: Write to merge log failed" << perror;
}
delete _log_pstream;
delete _log_stream;
delete _log;
}
if (_all_estrings) {
foreach (es_ptr, _all_estrings, DstringPtrArray::ArrayIterator)
delete (*es_ptr);
delete _all_estrings;
}
delete _deleted_markers;
delete_merge_parents (_merge_parents);
delete_merge_parents (_new_merge_parents);
if (_nullified_chars) delete _nullified_chars;
if (_populate_ignore) delete _populate_ignore;
if (_quick_elim) delete _quick_elim;
if (_project_keywords) delete _project_keywords;
if (_project_keywords_extra) delete _project_keywords_extra;
if (_keyword_id) delete _keyword_id;
if (_keyword_pheader) delete _keyword_pheader;
if (_keyword_pversion) delete _keyword_pversion;
if (_file_name_table) delete _file_name_table;
if (_file_desc_table) delete _file_desc_table;
if (_file_match_cache) delete _file_match_cache;
}
ProjectDescriptor::ProjectDescriptor()
:_prj_source_name(NULL),
_read_flags(KeepNothing),
_quick_elim(NULL),
_project_name(NULL),
_populate_ignore(NULL),
_segment(NULL),
_nullified_chars(NULL),
_all_estrings(NULL),
_files_insertion_point(NULL),
_project_version_point(NULL),
_end_of_buffer_point(NULL),
_project_keywords_point(NULL),
_populate_ignore_point(NULL),
_merge_parents_marker(0, 0),
_new_merge_parents_marker(0, 0),
_descends_from_marker(0, 0),
_project_keywords_marker(0, 0),
_populate_ignore_marker(0, 0),
_deleted_markers(NULL),
_repository_entry(NULL),
_file_entries(NULL),
_project_version_data(NULL),
_log_pstream(NULL),
_log_stream(NULL),
_log(NULL),
_project_version_name(NULL),
_project_version_major(NULL),
_project_version_minor(NULL),
_parent_version_name(NULL),
_parent_version_major(NULL),
_parent_version_minor(NULL),
_project_description(NULL),
_version_log(NULL),
_new_version_log(NULL),
_checkin_time(NULL),
_checkin_login(NULL),
_created_by_major(NULL),
_created_by_minor(NULL),
_created_by_micro(NULL),
_merge_parents(NULL),
_new_merge_parents(NULL),
_project_keywords(NULL),
_project_keywords_extra(NULL),
_keyword_id(NULL),
_keyword_pheader(NULL),
_keyword_pversion(NULL),
_merge_entry(NULL),
_merge_selected_major(NULL),
_merge_selected_minor(NULL),
_append_merge_parents(false),
_alter_popkey(false),
_merge_complete(false),
_merge_incomplete(false),
_file_name_table(NULL),
_file_desc_table(NULL),
_file_match_cache(NULL) { }
PrVoidError ProjectDescriptor::bad_project_file(const Sexp *s, const char* mes)
{
pthrow prcserror << fileline(_prj_source_name, s->line_number())
<< "Invalid project file, " << mes << dotendl;
}
PrProjectDescriptorPtrError read_project_file(const char* project_full_name,
const char* filename,
bool is_working,
ProjectReadData flags)
{
ProjectDescriptor* new_project = new ProjectDescriptor();
Return_if_fail(new_project->init_from_file(project_full_name, filename, is_working, NULL, flags));
return new_project;
}
PrProjectDescriptorPtrError read_project_file(const char* project_full_name,
const char* file_name,
bool is_working,
FILE* infile,
ProjectReadData flags)
{
ProjectDescriptor* new_project = new ProjectDescriptor();
Return_if_fail(new_project->init_from_file(project_full_name, file_name, is_working, infile, flags));
return new_project;
}
PrVoidError ProjectDescriptor::init_repository_entry(const char* file, bool lock, bool create)
{
Return_if_fail(_repository_entry << Rep_init_repository_entry(file, lock, create, true));
return NoError;
}
PrVoidError ProjectDescriptor::init_from_file(const char* full_name,
const char* filename,
bool is_working,
FILE* infile0,
ProjectReadData flags)
{
const char* last_slash;
FILE* infile;
_read_flags = flags;
_project_full_name.assign(full_name);
_project_file_path.assign(full_name);
_project_file_path.append(".prj");
last_slash = strrchr(full_name, '/');
if(last_slash) {
_project_name = last_slash + 1;
_project_path.assign(full_name);
_project_path.truncate(last_slash - full_name + 1);
} else {
_project_name = full_name;
/*_project_path.truncate(0);*/
}
_project_aux_path.sprintf("%s.%s.prcs_aux", _project_path.cast(), _project_name);
if(infile0 == NULL) {
struct stat buf;
if((infile = fopen(filename, "r")) == NULL)
pthrow prcserror << "Can't open file " << squote(filename) << perror;
If_fail(Err_stat(filename, & buf))
pthrow prcserror << "Stat failed on project file "
<< squote (filename) << perror;
_read_mode = buf.st_mode & 0777;
} else {
infile = infile0;
_read_mode = 0;
}
_prj_source_name = filename;
if (_read_flags & KeepMergeParents) {
_new_merge_parents = new MergeParentEntryPtrArray;
_merge_parents = new MergeParentEntryPtrArray;
}
_populate_ignore = new OrderedStringTable;
_project_keywords = new KeywordTable;
_project_keywords_extra = new OrderedStringTable;
_file_entries = new FileEntryPtrArray;
_deleted_markers = new ConstListMarkerPtrArray;
_attrs_table = new AttrsTable (attrs_hash, attrs_equal);
Return_if_fail(parse_prj_file(infile));
set_full_version (is_working);
if(infile0 == NULL)
fclose(infile);
return NoError;
}
void ProjectDescriptor::set_full_version (bool is_working)
{
_full_version.assign (*project_version_major());
_full_version.append ('.');
_full_version.append (*project_version_minor());
if (is_working)
_full_version.append ("(w)");
}
PrVoidError ProjectDescriptor::verify_merge_parents()
{
foreach (ent_ptr, _merge_parents, MergeParentEntryPtrArray::ArrayIterator) {
MergeParentEntry *entry = *ent_ptr;
Return_if_fail (entry->project_data << check_version_name (entry->selected_version, entry->lineno));
}
foreach (ent_ptr, _new_merge_parents, MergeParentEntryPtrArray::ArrayIterator) {
MergeParentEntry *entry = *ent_ptr;
Return_if_fail (entry->project_data << check_version_name (entry->selected_version, entry->lineno));
}
return NoError;
}
PrVoidError ProjectDescriptor::init_merge_log()
{
const char* logname_env = get_environ_var ("PRCS_MERGE_LOG");
Dstring logname;
if (logname_env)
logname.assign (logname_env);
else {
logname.assign (project_full_name());
logname.append (".log");
}
_log_stream = new filebuf;
if (!_log_stream->open(logname, ios::out|ios::app)) {
pthrow prcserror << "Failed opening merge log file " << squote(logname) << perror;
}
_log_pstream = new PrettyStreambuf (_log_stream, &option_report_actions);
_log_pstream->set_fill_width (1<<20);
_log_pstream->set_fill_prefix ("");
_log = new PrettyOstream (_log_pstream, NoError);
return NoError;
}
/* format should be a string consisting of 'U', 'N', 'L', 'S', and
* 'A', for user, number, label, string and any. returns false if a
* match fails. arguments following format are the addresses of
* Dstring*s to fill in. an example is:
*
* sexp_scan(s, "LLL", &d1, &d2, &d3);
*
* where dN will get the Nth label of three.
*
* if the first character of format is an asterisk, then "-*-" is
* accepted as a label. if the first cahracter of format is an 'e',
* then empty lists are allowed. */
static bool sexp_scan(const Sexp* list, const char* format, ...)
{
va_list args;
char c;
int len = list->length();
const char* type = NULL;
bool accept_ast = false;
bool accept_empty = false;
bool loop_done = false;
while (!loop_done) {
switch (*format) {
case '*':
accept_ast = true;
format += 1;
break;
case 'e':
accept_empty = true;
format += 1;
break;
default:
loop_done = true;
break;
}
}
va_start(args, format);
if(!list->is_pair()) goto error;
while((c = *format++)) {
switch(c) {
case 'N': type = "number"; break;
case 'S': type = "any"; break;
case 'L': type = "label"; break;
case 'U': type = "login"; break;
case 'A': type = "any"; break;
}
/* If there are no characters left, and we are expecting one, bail */
if(len == 0) goto error;
bool okay_empty = false;
if(list->car()->is_pair()) {
if (accept_empty && !list->car())
okay_empty = true;
else
goto error;
}
/* now check the type */
if(!okay_empty && VC_check_token_match(*list->car()->key(), type) <= 0) {
if(!(accept_ast && strcmp(*list->car()->key(), "-*-") == 0))
goto error;
}
if (c == 'S' && list->car()->key()->type())
goto error;
Estring** dptr = va_arg(args, Estring**);
if (dptr) {
if (okay_empty)
(*dptr) = NULL;
else
(*dptr) = list->car()->key();
}
list = list->cdr();
len -= 1;
}
va_end(args);
return len == 0;
error:
va_end(args);
return false;
}
/* Project read functions
*/
PrVoidError ProjectDescriptor::created_by_prj_entry_func(const Sexp* s)
{
if(!sexp_scan(s, "LNNN", NULL, &_created_by_major,
&_created_by_minor, &_created_by_micro ))
return bad_project_file(s, "badly formed Created-By-Prcs-Version entry");
return NoError;
}
PrVoidError ProjectDescriptor::project_ver_prj_entry_func(const Sexp* s)
{
if(!sexp_scan(s, "LLLN", NULL, &_project_version_name,
&_project_version_major, &_project_version_minor ))
return bad_project_file(s, "badly formed Project-Version entry");
_project_version_point = new Estring (this, s->end_index());
return NoError;
}
PrVoidError ProjectDescriptor::parent_ver_prj_entry_func(const Sexp* s)
{
if(!sexp_scan(s, "*LLLN", NULL, &_parent_version_name,
&_parent_version_major, &_parent_version_minor )) {
return bad_project_file(s, "badly formed Parent-Version entry");
} else {
bool major_absent = strcmp(*_parent_version_name, "-*-") == 0;
bool minor_absent = strcmp(*_parent_version_major, "-*-") == 0;
bool name_absent = strcmp(*_parent_version_minor, "-*-") == 0;
if(major_absent ^ minor_absent ||
major_absent ^ name_absent ||
name_absent ^ minor_absent)
return bad_project_file(s, "illegal use of -*-");
}
return NoError;
}
PrVoidError ProjectDescriptor::project_desc_prj_entry_func(const Sexp* s)
{
if(!sexp_scan(s, "LS", NULL, &_project_description))
return bad_project_file(s, "badly formed Project-Description entry");
return NoError;
}
PrVoidError ProjectDescriptor::version_log_prj_entry_func(const Sexp* s)
{
if(!sexp_scan(s, "LS", NULL, &_version_log)) {
return bad_project_file(s, "badly formed Version-Log entry");
}
return NoError;
}
PrVoidError ProjectDescriptor::new_version_log_prj_entry_func(const Sexp* s)
{
if(!sexp_scan(s, "LS", NULL, &_new_version_log))
return bad_project_file(s, "badly formed New-Version-Log entry");
return NoError;
}
PrVoidError ProjectDescriptor::checkin_time_prj_entry_func(const Sexp* s)
{
if(!sexp_scan(s, "LS", NULL, &_checkin_time)) {
return bad_project_file(s, "badly formed Checkin-Time entry");
}
return NoError;
}
PrVoidError ProjectDescriptor::checkin_login_prj_entry_func(const Sexp* s)
{
if(!sexp_scan(s, "LU", NULL, &_checkin_login))
return bad_project_file(s, "badly formed Checkin-Login entry");
return NoError;
}
PrVoidError ProjectDescriptor::descends_from_prj_entry_func(const Sexp* s)
{
/* Backward compatibility, its just ignored. */
_descends_from_marker = ListMarker (s->start_index(), s->end_index());
_deleted_markers->append (&_descends_from_marker);
return NoError;
}
PrVoidError ProjectDescriptor::populate_ignore_prj_entry_func(const Sexp* s)
{
_populate_ignore_marker = ListMarker (s->start_index(), s->end_index());
_populate_ignore_point = new Estring (this, s->end_index());
if(s->length() != 2 || !s->cadr()->is_pair())
return bad_project_file(s, "malformed Populate-Ignore entry");
foreach_sexp (t_ptr, s->cadr()) {
const Sexp *t = *t_ptr;
if(t->is_pair() || t->key()->type() != EsStringLiteral)
return bad_project_file(t, "expected a string");
}
foreach_sexp (t_ptr, s->cadr()) {
Dstring* ds = (*t_ptr)->key();
const char* t = ds->cast();
_populate_ignore->insert (t, t);
}
return NoError;
}
PrVoidError ProjectDescriptor::project_keywords_prj_entry_func (const Sexp* s)
{
_project_keywords_marker = ListMarker (s->start_index(), s->end_index());
_project_keywords_point = new Estring (this, s->end_index());
foreach_sexp (t_ptr, s->cdr()) {
const Sexp *t = *t_ptr;
if(!t->is_pair() ||
t->length() != 2 ||
t->car()->is_pair() ||
t->cadr()->is_pair() ||
t->car()->key()->type() != EsNameLiteral)
return bad_project_file(t, "malformed Project-Keywords entry");
if (is_builtin_keyword (t->car()->key()->cast(), t->car()->key()->length()))
return bad_project_file(t, "Project-Keywords may not contain a builtin keyword");
if (_project_keywords->isdefined (t->car()->key()->cast()))
return bad_project_file(t, "duplicate keyword");
if (strchr (t->cadr()->key()->cast(), '\n'))
return bad_project_file(t, "keyword value may not contain a `$' character");
_project_keywords->insert (t->car()->key()->cast(), t->cadr()->key()->cast());
_project_keywords_extra->insert (t->car()->key()->cast(), t->cadr()->key()->cast());
}
return NoError;
}
PrVoidError ProjectDescriptor::new_merge_parents_prj_entry_read (int offset)
{
_new_merge_parents_marker.start = offset;
Return_if_fail (read_merge_parents(_new_merge_parents, &_new_merge_parents_marker));
return NoError;
}
PrVoidError ProjectDescriptor::merge_parents_prj_entry_read(int offset)
{
_merge_parents_marker.start = offset;
Return_if_fail (read_merge_parents(_merge_parents, &_merge_parents_marker));
return NoError;
}
PrVoidError ProjectDescriptor::files_prj_entry_read(int /*offset*/)
{
const Sexp* s;
DstringPtrArray gtags;
bool allow_tags = true;
Return_if_fail (s << read_list_elt());
while (!s->is_empty()) {
if (s->is_pair()) {
Return_if_fail(individual_insert_fileent(s, >ags));
allow_tags = false;
} else if (allow_tags && s->key()->index(0) == ':') {
gtags.append (s->key());
} else {
return bad_project_file(s, "unexpected or illegal file attribute");
}
delete s;
Return_if_fail(s << read_list_elt());
}
/* @@@ */
_files_insertion_point = new Estring(this, prj_lex_this_index);
Return_if_fail(verify_project_file());
return NoError;
}
/* Read Helpers.
*/
static ListMarker *finish_me = NULL;
PrjTokenType prj_lex()
{
int val = prjlex();
if (finish_me) {
finish_me->stop = prj_lex_this_index + (val == PrjEof);
finish_me = NULL;
}
return (PrjTokenType) val;
}
PrVoidError ProjectDescriptor::read_merge_parents (MergeParentEntryPtrArray* array,
ListMarker* finish)
{
PrjTokenType tok;
while (true) {
tok = prj_lex();
switch (tok) {
case PrjNull:
case PrjBadString:
case PrjEof:
case PrjString:
case PrjName:
return read_parse_bad_token(tok);
case PrjClose:
finish_me = finish;
return NoError;
case PrjOpen:
Return_if_fail (read_merge_parents_entry (array));
}
}
return NoError;
}
PrVoidError ProjectDescriptor::read_merge_parents_file_name (const Sexp* s, Estring** fill)
{
if (s->is_pair() && s->length() == 0)
*fill = NULL;
else if (!s->is_pair())
*fill = s->key();
else
return bad_project_file (s, "bad merge parents entry");
return NoError;
}
PrVoidError ProjectDescriptor::read_merge_parents_entry (MergeParentEntryPtrArray* array)
{
const Sexp* s;
MergeParentState state = MergeStateParent;
MergeParentEntry* entry = NULL;
MergeParentFile* file;
Dstring* selected_version = NULL;
Estring* working = NULL;
Estring* common = NULL;
Estring* selected = NULL;
MergeAction action;
Return_if_fail (s << read_list_elt());
if (s->is_pair() || !strchr (s->key()->cast(), '.'))
return bad_project_file (s, "expected version number");
selected_version = s->key();
delete s;
Return_if_fail (s << read_list_elt());
/* So, if we encounter 3 elements, its the stupid 1.1 format, 1
* element, its the stupid 1.0 and earlier format, 2 elements and
* its the (hopefully less stupid) current format. */
if (!s->is_pair()) {
/* 1.1 or 1.2 format. */
const Sexp* elt_2 = s;
const Sexp* elt_3;
Return_if_fail (elt_3 << read_list_elt());
if (elt_3->is_pair()) {
/* 1.2 format. */
if (strcmp (elt_2->key()->cast(), "complete") == 0) {
state = MergeStateParent;
} else if (strcmp (elt_2->key()->cast(), "incomplete-parent") == 0) {
state = MergeStateIncompleteParent;
} else if (strcmp (elt_2->key()->cast(), "incomplete") == 0) {
state = MergeStateIncomplete;
} else {
return bad_project_file (elt_2, "expected completion state");
}
delete elt_2;
s = elt_3;
} else {
/* 1.1 format. */
if (strcmp (elt_3->key()->cast(), "complete") == 0) {
state = MergeStateParent;
} else if (strcmp (elt_3->key()->cast(), "incomplete") == 0) {
state = MergeStateIncomplete;
} else {
return bad_project_file (elt_3, "expected completion state");
}
delete elt_2;
delete elt_3;
Return_if_fail (s << read_list_elt());
}
}
if (array) {
entry = new MergeParentEntry (*selected_version,
state,
prj_lineno);
array->append (entry);
}
while (!s->is_empty()) {
if (!s->is_pair() || s->length() != 4)
return bad_project_file (s, "bad merge parents entry");
Return_if_fail (read_merge_parents_file_name (s->car(), &working));
Return_if_fail (read_merge_parents_file_name (s->cadr(), &common));
Return_if_fail (read_merge_parents_file_name (s->caddr(), &selected));
if (s->cadddr()->is_pair() || s->cadddr()->key()->length() != 1)
return bad_project_file (s, "bad merge parents entry");
switch (s->cadddr()->key()->index(0)) {
case 'a': case 'd': case 'n': case 'm': case 'r':
action = (MergeAction) s->cadddr()->key()->index(0);
break;
default:
return bad_project_file (s, "bad merge parents entry");
}
if (entry) {
file = new MergeParentFile (working ? working->cast() : NULL,
common ? common->cast() : NULL,
selected ? selected->cast() : NULL,
action);
entry->all_files.append (file);
}
delete s;
Return_if_fail (s << read_list_elt());
}
return NoError;
}
PrVoidError ProjectDescriptor::individual_insert_fileent(const Sexp* file, const DstringPtrArray* gtags)
{
Dstring *working_path = NULL;
Dstring *desc_name = NULL;
Dstring *version_num = NULL;
Dstring *link_name = NULL;
const Sexp *file_descriptor = NULL;
mode_t file_mode = 0644;
ListMarker ent_marker;
DstringPtrArray tags;
const PrcsAttrs* attrs;
if(!file->is_pair())
return bad_project_file(file, "expected a list");
if(file->length() < 2)
return bad_project_file(file, "file entry too short");
if(file->car()->is_pair())
return bad_project_file(file, "expected a filename");
if(!file->cadr()->is_pair())
return bad_project_file(file, "malformed internal file identifier");
if(weird_pathname(*file->car()->key()))
return bad_project_file(file, "illegal pathname");
if (project_path()[0]) {
/* This saves allocating a new string if its going to be the
* same as the one in the project file. */
working_path = new Dstring(project_path());
working_path->append(*file->car()->key());
} else {
working_path = file->car()->key();
}
ent_marker = file->marker();
file_descriptor = file->cadr();
foreach_sexp (elt_ptr, file_descriptor) {
if ((*elt_ptr)->is_pair())
return bad_project_file (*elt_ptr, "malformed internal file descriptor");
}
tags.append (*gtags);
foreach_sexp (tag_ptr, file->cdr()->cdr()) {
const Sexp* tag = *tag_ptr;
if(tag->is_pair() || tag->key()->index(0) != ':')
return bad_project_file(file, "invalid file attribute");
tags.append ((*tag_ptr)->key());
}
Return_if_fail (attrs << intern_attrs (&tags, gtags->length(), working_path->cast(), true));
if ( attrs->type() == Directory || attrs->type() == SymLink ) {
if (attrs->type() == Directory) {
if (file_descriptor->length() > 0)
return bad_project_file(file, "malformed internal file descriptor");
} else {
if (file_descriptor->length() > 1)
return bad_project_file(file, "malformed internal file descriptor");
if (file_descriptor->length() == 1)
link_name = file_descriptor->car()->key();
}
} else {
if(file_descriptor->length() == 0) {
/* its empty */
} else {
if(file_descriptor->length() == 2) {
/* its the old format -- no file mode */
} else if(file_descriptor->length() == 3) {
const char *mode_str = *file_descriptor->cdr()->cadr()->key();
file_mode = 0;
if(strlen(mode_str) != 3)
return bad_project_file(file, "invalid file mode");
while(*mode_str) {
char c = *mode_str++;
if (c < '8' && c >= '0') {
file_mode += c - '0';
if(*mode_str)
file_mode <<= 3;
} else {
return bad_project_file(file, "invalid file mode");
}
}
} else {
return bad_project_file(file, "malformed internal file descriptor");
}
version_num = file_descriptor->cadr()->key();
if (strncmp (*version_num, "1.", 2) != 0)
return bad_project_file (file, "malformed internal file descriptor");
Estring *int_desc = file_descriptor->car()->key();
const char* int_desc_c = *int_desc;
const char* first_slash = strchr(int_desc_c, '/');
if(!first_slash || strlen(first_slash) < 4) /* /0_a is legal (4 chars) */
return bad_project_file(file, "malformed internal file descriptor");
int diff = (first_slash + 1) - int_desc_c;
desc_name = new Estring (this,
(char*)int_desc_c + diff,
int_desc->offset() + diff,
int_desc->offset_length() - diff,
EsNameLiteral);
}
}
_file_entries->append(new FileEntry(working_path,
ent_marker,
attrs,
version_num,
desc_name,
link_name,
file_mode));
return NoError;
}
PrVoidError ProjectDescriptor::verify_project_file()
{
/* This code insures:
* no descriptor matches the project file's.
* no filename matches the project file's.
* no descriptor matches another.
* no filename matches another.
* no filename is a prefix of another unless it has :directory
*/
Dstring project_descriptor;
project_descriptor.sprintf("%s.prj", project_name());
/* Using the project_file_path and project_file_aux will
* be wrong later */
Dstring project_file_name(project_file_path());
Dstring project_aux_name(project_aux_path());
PathTable descriptors(pathname_hash, pathname_equal),
filenames (pathname_hash, pathname_equal);
descriptors.insert(project_descriptor.cast(), RealFile);
filenames.insert(project_file_name.cast(), RealFile);
filenames.insert(project_aux_name.cast(), RealFile);
foreach_fileent(fe_ptr, this) {
FileEntry* fe = *fe_ptr;
const char* fe_name = fe->working_path();
if(filenames.isdefined(fe_name)) {
if(strcmp(fe_name, project_file_name) == 0)
pthrow prcserror << "Entry " << squote(fe_name)
<< " duplicates project file name" << dotendl;
else if (strcmp(fe_name, project_aux_name) == 0)
pthrow prcserror << "Entry " << squote(fe_name)
<< " duplicates project auxiliary file name" << dotendl;
else
pthrow prcserror << "Entry " << squote(fe_name)
<< " is a duplicate file name" << dotendl;
}
filenames.insert(fe_name, fe->file_type());
if(fe->file_type() != RealFile || !fe->descriptor_name())
continue;
const char* fe_desc = fe->descriptor_name();
if(descriptors.isdefined(fe_desc)) {
if(strcmp(fe_desc, project_descriptor) == 0)
pthrow prcserror << "Entry " << squote(fe_name)
<< " duplicates project file descriptor" << dotendl;
else
pthrow prcserror << "Entry " << squote(fe_name)
<< " contains a duplicate file descriptor" << dotendl;
}
descriptors.insert(fe_desc, RealFile);
}
foreach_fileent(fe_ptr, this) {
FileEntry* fe = *fe_ptr;
Dstring name(fe->working_path());
for(int i = name.length() - 1; i > 0; i -= 1) {
if (name.index(i) == '/') {
name.truncate(i);
FileType* ft = filenames.lookup(name);
if (ft && (*ft) != Directory)
pthrow prcserror << "Non-directory filename " << squote(name)
<< " is prefix to another file" << dotendl;
}
}
}
return NoError;
}
/* merge_notify, has_been_merged --
*
* these two functions deserve documentation. merge_notify is called to
* update the project file immediately after a merge action is taken, in
* merge_finish. the MergeAction argument identifies the type of action
* taken. the goal is to save all information and avoid offering to merge
* a particular working file twice. The data saved is as follows:
*
* (working_name common_name selected_name action)
*
* file families are not saved because for common and selected versions
* they are well defined. for working files, they are not guaranteed to
* exist. both names and families may change with merges and users edits
* so tracking merges by them is not useful, in my opinion.
*/
void ProjectDescriptor::merge_notify(FileEntry* working,
FileEntry* common,
FileEntry* selected,
MergeAction ma) const
{
MergeParentFile* new_entry;
new_entry = new MergeParentFile (working ? working->working_path() + cmd_root_project_path_len : NULL,
common ? common->working_path() + cmd_root_project_path_len : NULL,
selected ? selected->working_path() + cmd_root_project_path_len : NULL,
ma);
_merge_entry->all_files.append (new_entry);
}
/* algorithm: to be true,
* for each of w,c,s, if a lookup of filename in the correct
* table must succeed.
*/
bool ProjectDescriptor::has_been_merged(FileEntry* working,
FileEntry* common,
FileEntry* selected) const
{
return
(working && _merge_entry->working_table ->isdefined(working ->working_path() + cmd_root_project_path_len)) ||
(common && _merge_entry->common_table ->isdefined(common ->working_path() + cmd_root_project_path_len)) ||
(selected && _merge_entry->selected_table->isdefined(selected->working_path() + cmd_root_project_path_len));
}
bool ProjectDescriptor::merge_continuing (ProjectVersionData *selected)
{
_merge_selected_major = selected->prcs_major();
_merge_selected_minor = selected->prcs_minor();
Dstring sel_version_name;
bool return_val = false;
sel_version_name.append (selected->prcs_major());
sel_version_name.append ('.');
sel_version_name.append (selected->prcs_minor());
_deleted_markers->append (&_merge_parents_marker);
_deleted_markers->append (&_new_merge_parents_marker);
_append_merge_parents = true;
_merge_entry = new MergeParentEntry (sel_version_name, MergeStateIncomplete, 0);
_new_merge_parents->append (_merge_entry);
_merge_entry->working_table = new MergeParentTable;
_merge_entry->common_table = new MergeParentTable;
_merge_entry->selected_table = new MergeParentTable;
foreach (ent_ptr, _new_merge_parents, MergeParentEntryPtrArray::ArrayIterator) {
MergeParentEntry *entry = *ent_ptr;
if (strcmp (entry->selected_version, sel_version_name) != 0)
continue;
foreach (file_ptr, &entry->all_files, MergeParentFilePtrArray::ArrayIterator) {
MergeParentFile *file = *file_ptr;
return_val = true;
if (file->working) _merge_entry->working_table ->insert (file->working , file);
if (file->common) _merge_entry->common_table ->insert (file->common , file);
if (file->selected) _merge_entry->selected_table->insert (file->selected, file);
}
}
return return_val;
}
void ProjectDescriptor::merge_finish()
{
if(!_merge_complete || _merge_incomplete) {
prcsinfo << "Merge incomplete" << dotendl;
merge_log() << prcsendl << "*** Merge incomplete" << prcsendl << prcsendl;
} else {
_merge_entry->state = MergeStateParent;
prcsinfo << "Merge against version "
<< _merge_selected_major << "."
<< _merge_selected_minor << " complete" << dotendl;
merge_log() << prcsendl << "*** Merge complete" << prcsendl << prcsendl;
}
}
void ProjectDescriptor::delete_merge_parents (MergeParentEntryPtrArray* array)
{
if (array) {
foreach (ent_ptr, array, MergeParentEntryPtrArray::ArrayIterator) {
MergeParentEntry *entry = *ent_ptr;
if (entry->working_table) delete entry->working_table;
if (entry->common_table) delete entry->common_table;
if (entry->selected_table) delete entry->selected_table;
foreach (file_ptr, &entry->all_files, MergeParentFilePtrArray::ArrayIterator)
delete (*file_ptr);
}
}
}
PrProjectVersionDataPtrError ProjectDescriptor::check_version_name(const char* name, int line) const
{
ProjectVersionData* data;
const char* this_version_major = major_version_of(name);
const char* this_version_minor = minor_version_of(name);
if(!this_version_major)
pthrow prcserror << fileline(_prj_source_name, line)
<< "Illegal version number " << name << dotendl;
if(VC_check_token_match(this_version_major, "label") <= 0 ||
VC_check_token_match(this_version_minor, "number") <= 0)
pthrow prcserror << fileline(_prj_source_name, line)
<< "Illegal version number " << name << dotendl;
data = repository_entry()->lookup_version(this_version_major, this_version_minor);
if (!data)
pthrow prcserror << fileline(_prj_source_name, line)
<< "Version " << name
<< " not found in repository" << dotendl;
return data;
}
PrVoidError ProjectDescriptor::adjust_merge_parents()
{
_deleted_markers->append (&_merge_parents_marker);
_deleted_markers->append (&_new_merge_parents_marker);
_append_merge_parents = true;
Return_if_fail(verify_merge_parents());
delete_merge_parents (_merge_parents);
_merge_parents = _new_merge_parents;
_new_merge_parents = NULL;
return NoError;
}
/* Keywords
*/
void ProjectDescriptor::add_keyword (const char* key, const char* val)
{
_alter_popkey = true;
ASSERT (! _project_keywords_extra->lookup (key), "blah");
_project_keywords->insert (key, val);
_project_keywords_extra->insert (key, val);
}
void ProjectDescriptor::rem_keyword (const char* key)
{
_alter_popkey = true;
ASSERT (_project_keywords_extra->lookup (key), "blah");
_project_keywords->remove (key);
_project_keywords_extra->remove (key);
}
void ProjectDescriptor::set_keyword (const char* key, const char* val)
{
_alter_popkey = true;
ASSERT (_project_keywords_extra->lookup (key), "blah");
_project_keywords->insert (key, val);
_project_keywords_extra->insert (key, val);
}
void ProjectDescriptor::add_populate_ignore (const char* key)
{
_alter_popkey = true;
ASSERT (! _populate_ignore->lookup (key), "blah");
_populate_ignore->insert (key, key);
}
void ProjectDescriptor::rem_populate_ignore (const char* key)
{
_alter_popkey = true;
ASSERT (_populate_ignore->lookup (key), "blah");
_populate_ignore->remove (key);
}
OrderedStringTable* ProjectDescriptor::project_keywords_extra() const
{
return _project_keywords_extra;
}
KeywordTable* ProjectDescriptor::project_keywords(FileEntry* fe, bool setting)
{
if (!_keyword_id) {
_keyword_id = new Dstring;
_keyword_pversion = new Dstring;
_keyword_pheader = new Dstring;
} else if (!setting) {
/* Values don't matter, just the keywords. */
return _project_keywords;
}
if (setting) {
ASSERT(fe->last_mod_date() != NULL &&
fe->last_mod_user() != NULL, "missing entries in file entry");
_keyword_id->sprintf("%s %s %s %s",
strip_leading_path(fe->working_path()),
fe->descriptor_version_number(),
fe->last_mod_date(),
fe->last_mod_user());
_project_keywords->insert("Id", _keyword_id->cast());
_project_keywords->insert("Author", fe->last_mod_user());
_project_keywords->insert("Date", fe->last_mod_date());
_project_keywords->insert("Basename", strip_leading_path(fe->working_path()));
_project_keywords->insert("Revision", fe->descriptor_version_number());
_project_keywords->insert("Source", fe->working_path());
} else {
_project_keywords->insert("Id", "");
_project_keywords->insert("Author", "");
_project_keywords->insert("Date", "");
_project_keywords->insert("Basename", "");
_project_keywords->insert("Revision", "");
_project_keywords->insert("Source", "");
}
if (_keyword_pversion->length() == 0) {
_keyword_pversion->sprintf("%s.%s",
project_version_major()->cast(),
project_version_minor()->cast());
_keyword_pheader->sprintf("%s %s %s %s",
project_name(),
_keyword_pversion->cast(),
checkin_time()->cast(),
checkin_login()->cast());
_project_keywords->insert("Project", project_name());
_project_keywords->insert("ProjectDate", checkin_time()->cast());
_project_keywords->insert("ProjectAuthor", checkin_login()->cast());
_project_keywords->insert("ProjectMajorVersion", project_version_major()->cast());
_project_keywords->insert("ProjectMinorVersion", project_version_minor()->cast());
_project_keywords->insert("ProjectVersion", _keyword_pversion->cast());
_project_keywords->insert("ProjectHeader", _keyword_pheader->cast());
}
return _project_keywords;
}
/* File Insertion
*/
void ProjectDescriptor::append_files_data (const char* data)
{
_files_insertion_point->append (data);
}
void ProjectDescriptor::append_file (const char* working_path,
bool keywords)
{
_files_insertion_point->append ("\n (");
_files_insertion_point->append_protected (working_path);
_files_insertion_point->append (" ()");
if (keywords)
_files_insertion_point->append (" :no-keywords");
_files_insertion_point->append (")");
}
void ProjectDescriptor::append_link (const char* working_path,
const char* link_name)
{
_files_insertion_point->append ("\n (");
_files_insertion_point->append_protected (working_path);
_files_insertion_point->append (" (");
_files_insertion_point->append_protected (link_name);
_files_insertion_point->append (") :symlink)");
}
void ProjectDescriptor::append_directory (const char* working_path)
{
_files_insertion_point->append ("\n (");
_files_insertion_point->append_protected (working_path);
_files_insertion_point->append (" () :directory)");
}
void ProjectDescriptor::append_file_deletion (FileEntry* fe)
{
_files_insertion_point->append ("\n ; (");
_files_insertion_point->append_protected (fe->working_path());
_files_insertion_point->append (" ()");
fe->file_attrs()->print_to_string (_files_insertion_point, true);
_files_insertion_point->append (")");
}
void ProjectDescriptor::append_file (FileEntry* fe)
{
_files_insertion_point->append ("\n (");
_files_insertion_point->append_protected (fe->working_path());
if (fe->file_type() == RealFile && fe->descriptor_name()) {
_files_insertion_point->append (" (");
_files_insertion_point->append_protected (project_name());
_files_insertion_point->append ("/");
_files_insertion_point->append_protected (fe->descriptor_name());
_files_insertion_point->append (" ");
_files_insertion_point->append_protected (fe->descriptor_version_number());
_files_insertion_point->sprintfa (" %03o)", fe->file_mode());
} else {
_files_insertion_point->append (" ()");
}
fe->file_attrs()->print_to_string (_files_insertion_point, true);
_files_insertion_point->append (")");
}
/* Attributes */
PrcsAttrs::PrcsAttrs (const DstringPtrArray *vals0, int ngroup)
:_vals(*vals0),
_ngroup(ngroup),
_type(RealFile),
_project(NULL),
_keyword_sub(true)
{
memcpy (_merge_actions, default_merge_actions, sizeof (_merge_actions));
memcpy (_merge_descs, default_merge_descs , sizeof (_merge_descs));
const char* env;
env = get_environ_var ("PRCS_MERGE_COMMAND");
if (env)
_mergetool.assign (env);
env = get_environ_var ("PRCS_DIFF_COMMAND");
if (env)
_difftool.assign (env);
}
FileType PrcsAttrs::type() const { return _type; }
bool PrcsAttrs::keyword_sub() const { return _keyword_sub; }
ProjectDescriptor* PrcsAttrs::project() const { return _project; };
MergeAction PrcsAttrs::merge_action (int rule) const { return _merge_actions[rule]; };
const char* PrcsAttrs::merge_desc (int rule) const { return _merge_descs[rule]; };
const char* PrcsAttrs::merge_tool () const { return _mergetool.length() > 0 ? _mergetool.cast() : NULL; }
const char* PrcsAttrs::diff_tool () const { return _difftool.length() > 0 ? _difftool.cast() : NULL; }
void PrcsAttrs::print_to_string (Dstring* str, bool lead_if) const
{
if (lead_if && _vals.length() > _ngroup)
str->append (' ');
for (int i = _ngroup; i < _vals.length(); i += 1) {
str->append (_vals.index(i)->cast());
if (i < (_vals.length() - 1))
str->append (' ');
}
}
void PrcsAttrs::print (ostream& os, bool lead_if) const
{
if (lead_if && _vals.length() > _ngroup)
os << ' ';
for (int i = _ngroup; i < _vals.length(); i += 1) {
os << _vals.index(i);
if (i < (_vals.length() - 1))
os << ' ';
}
}
bool PrcsAttrs::regex_matches (reg2ex2_t *re) const
{
foreach (ds_ptr, &_vals, DstringPtrArray::ArrayIterator) {
if (prcs_regex_matches ((*ds_ptr)->cast(), re))
return true;
}
return false;
}
PrVoidError ProjectDescriptor::validate_attrs (const PrcsAttrs *attrs, const char* name)
{
bool seen_type = false;
foreach (ds_ptr, &attrs->_vals, DstringPtrArray::ArrayIterator) {
Dstring an = (*ds_ptr)->cast();
Dstring av;
const char* equal = strchr (an.cast(), '=');
if (equal) {
av.assign (equal + 1);
an.truncate (equal - an.cast());
}
const AttrDesc* ad = is_file_attribute (an.cast(), an.length());
if (!ad)
pthrow prcserror << "Unrecognized attribute " << an << " for file " << squote(name) << dotendl;
switch (ad->type) {
case RealFileAttr:
case DirectoryAttr:
case SymlinkAttr:
if (seen_type)
pthrow prcserror << "Duplicate file type attribute for file " << squote(name) << dotendl;
seen_type = true;
break;
case Mergerule1Attr:
case Mergerule2Attr:
case Mergerule3Attr:
case Mergerule4Attr:
case Mergerule5Attr:
case Mergerule6Attr:
case Mergerule7Attr:
case Mergerule8Attr:
case Mergerule9Attr:
case Mergerule10Attr:
case Mergerule11Attr:
case Mergerule12Attr:
case Mergerule13Attr:
case Mergerule14Attr:
if (av.length() != 1 || strchr("andrm", av.index(0)) == NULL)
pthrow prcserror << "Illegal merge action " << av << dotendl;
break;
case MergetoolAttr:
case DifftoolAttr:
if (av.length() < 1)
pthrow prcserror << "Illegal tool name " << av << dotendl;
default:
break;
}
}
return NoError;
}
void ProjectDescriptor::init_attrs (PrcsAttrs *attrs)
{
attrs->_project = this;
foreach (ds_ptr, &attrs->_vals, DstringPtrArray::ArrayIterator) {
Dstring an = (*ds_ptr)->cast();
Dstring av;
const char* equal = strchr (an.cast(), '=');
if (equal) {
av.assign (equal + 1);
an.truncate (equal - an.cast());
}
const AttrDesc* ad = is_file_attribute (an.cast(), an.length());
int mergerule = 0;
switch (ad->type) {
case RealFileAttr:
case ProjectFileAttr:
attrs->_type = RealFile;
break;
case DirectoryAttr:
case ImplicitDirectoryAttr:
attrs->_type = Directory;
break;
case SymlinkAttr:
attrs->_type = SymLink;
break;
case NoKeywordAttr:
attrs->_keyword_sub = false;
break;
case MergetoolAttr:
attrs->_mergetool.assign (av);
break;
case DifftoolAttr:
attrs->_difftool.assign (av);
break;
case TagAttr:
break;
case Mergerule14Attr: mergerule += 1;
case Mergerule13Attr: mergerule += 1;
case Mergerule12Attr: mergerule += 1;
case Mergerule11Attr: mergerule += 1;
case Mergerule10Attr: mergerule += 1;
case Mergerule9Attr: mergerule += 1;
case Mergerule8Attr: mergerule += 1;
case Mergerule7Attr: mergerule += 1;
case Mergerule6Attr: mergerule += 1;
case Mergerule5Attr: mergerule += 1;
case Mergerule4Attr: mergerule += 1;
case Mergerule3Attr: mergerule += 1;
case Mergerule2Attr: mergerule += 1;
case Mergerule1Attr: /* notice zero origin */
attrs->_merge_descs[mergerule] = "User supplied action, no help available";
attrs->_merge_actions[mergerule] = (MergeAction)av.index(0);
break;
}
}
}
PrPrcsAttrsPtrError ProjectDescriptor::intern_attrs (const DstringPtrArray* tagvals,
int ngroup,
const char* name,
bool validate)
{
PrcsAttrs *it = new PrcsAttrs (tagvals, ngroup);
PrcsAttrs **lu;
lu = _attrs_table->lookup (it);
if (lu) {
delete it;
return *lu;
} else {
if (validate)
Return_if_fail (validate_attrs (it, name));
init_attrs (it);
_attrs_table->insert (it, it);
return it;
}
}
bool attrs_equal(const PrcsAttrs*const& x, const PrcsAttrs*const& y)
{
if (x->_ngroup != y->_ngroup || x->_vals.length() != y->_vals.length())
return false;
for (int i = 0; i < x->_vals.length(); i += 1) {
if (strcmp (x->_vals.index(i)->cast(), y->_vals.index(i)->cast()) != 0)
return false;
}
return true;
}
extern int hash(const char *const& s, int M);
int attrs_hash(const PrcsAttrs*const & s, int M)
{
int h = 0;
const char* p;
for (int i = 0; i < s->_vals.length(); i += 1) {
p = s->_vals.index(i)->cast();
h += hash (p, M);
}
return h % M;
}
/* Updates and Deletions.
*/
PrVoidError ProjectDescriptor::write_project_file(const char* filename)
{
if (strcmp (*project_version_name(), project_name()) != 0) {
_project_version_point->sprintfa ("\n;; PRCS note: this project version was "
"originally checked in when\n;; the project "
"was named `%s'.\n",
project_version_name()->cast());
project_version_name()->assign(project_name());
}
project_version_name()->assign(project_name());
if (strcmp(parent_version_minor()->cast(), "-*-") != 0)
parent_version_name()->assign(project_name());
created_by_major()->assign_int(prcs_version_number[0]);
created_by_minor()->assign_int(prcs_version_number[1]);
created_by_micro()->assign_int(prcs_version_number[2]);
if (_alter_popkey) {
_deleted_markers->append (& _populate_ignore_marker);
_deleted_markers->append (& _project_keywords_marker);
int first = 0;
_project_keywords_point->append ("(Project-Keywords");
_populate_ignore_point->append ("(Populate-Ignore (");
int ks = _project_keywords_extra->key_array()->length();
for (int i = 0; i < ks; i += 1) {
int nspaces = (first * strlen ("(Project-Keywords")) + 1;
for (int j = 0; j < nspaces; j += 1)
_project_keywords_point->append (" ");
_project_keywords_point->append ("(");
_project_keywords_point->append_protected (_project_keywords_extra->key_array()->index(i));
_project_keywords_point->append (" ");
_project_keywords_point->append_string (_project_keywords_extra->data_array()->index(i));
_project_keywords_point->append (")");
if (i < (ks - 1))
_project_keywords_point->append ("\n");
first = 1;
}
first = 0;
ks = _populate_ignore->key_array()->length();
for (int i = 0; i < ks; i += 1) {
int nspaces = (first * strlen ("(PopulateIgnore ( "));
for (int j = 0; j < nspaces; j += 1)
_populate_ignore_point->append (" ");
_populate_ignore_point->append_string (_populate_ignore->key_array()->index(i));
if (i < (ks - 1))
_populate_ignore_point->append ("\n");
first = 1;
}
_project_keywords_point->append (")");
_populate_ignore_point->append ("))");
}
Return_if_fail(make_subdirectories(filename));
WriteableFile outfile;
Return_if_fail (outfile.open(filename));
really_write_project_file (outfile.stream());
Return_if_fail (outfile.close());
if (_read_mode != 0) {
If_fail (Err_chmod (filename, _read_mode))
prcswarning << "Could not reset permissions on file " << squote (filename) << dotendl;
}
return NoError;
}
static int sort_marker (const void* a, const void* b)
{
return (*((const ListMarker**)a ))->start -
(*((const ListMarker**)b ))->start;
}
void ProjectDescriptor::really_write_project_file (ostream& os)
{
int buffer_index = 0;
int buffer_length = _segment->length() - 1 /* the null */;
int file_number = 0;
int marker_number = 0;
int estring_number = 0;
int file_number_max = _file_entries->length();
int marker_number_max = 0;
int estring_number_max = _all_estrings->length();
if (_deleted_markers) {
marker_number_max = _deleted_markers->length();
/* File entries and Estrings are in sorted order, they are read that way. */
/* Markers must be sorted. */
_deleted_markers->sort(sort_marker);
}
int next_file_index;
int next_marker_index;
int next_estring_index;
int next_index;
while (buffer_index <= buffer_length) {
/* markers are never skipped, so get the next index. */
if (marker_number < marker_number_max)
next_marker_index = _deleted_markers->index(marker_number)->start;
else
next_marker_index = buffer_length + 1;
/* estrings and files may be skipped by markers, so if the index
* found is less than the buffer_index, then its been skipped and
* we incr their number until one ahead of the point is found. */
do {
if (file_number < file_number_max)
next_file_index = _file_entries->index(file_number)->ent_marker()->start;
else
next_file_index = buffer_length + 1;
} while (next_file_index < buffer_index && (file_number += 1, true));
do {
if (estring_number < estring_number_max)
next_estring_index = ((Estring*)_all_estrings->index(estring_number))->offset();
else
next_estring_index = buffer_length + 1;
} while (next_estring_index < buffer_index && (estring_number += 1, true));
next_index = next_estring_index < next_file_index ? next_estring_index : next_file_index;
next_index = next_index < next_marker_index ? next_index : next_marker_index;
ASSERT (buffer_index <= next_index, "no way man!");
if (next_index > buffer_length)
break;
if (buffer_index < next_index) {
os.write (_segment->segment() + buffer_index, next_index - buffer_index );
buffer_index = next_index;
} else if (next_index == next_marker_index) {
const ListMarker* marker = _deleted_markers->index(marker_number++);
buffer_index = marker->stop;
} else if (next_index == next_estring_index) {
Estring* string = (Estring*) _all_estrings->index(estring_number);
char missing_char = _nullified_chars->index(estring_number++);
switch (string->type()) {
case EsStringLiteral:
for (int i = 0; i < string->length(); i += 1) {
char c = string->index(i);
if (c == '\"' || c == '\\')
os << '\\';
os << c;
}
ASSERT (missing_char == '\"', "must be");
os << missing_char;
buffer_index = string->offset() + string->offset_length() + 1;
break;
case EsNameLiteral:
print_protected (string->cast(), os);
os << missing_char;
buffer_index = string->offset() + string->offset_length() + 1;
break;
case EsUnProtected:
os << string;
break;
}
} else /* (next_index == next_file_index) */ {
FileEntry *fe = _file_entries->index (file_number++);
os << "(";
print_protected(fe->working_path(), os);
os << " (";
if (fe->file_type() == SymLink && fe->link_name()) {
print_protected(fe->link_name(), os);
} else if (fe->file_type() == RealFile &&
fe->descriptor_name() &&
fe->descriptor_version_number()[0] != 0) {
print_protected (project_name(), os);
os << "/";
print_protected (fe->descriptor_name(), os);
char buf[8];
sprintf(buf, "%03o", fe->file_mode());
os << " " << fe->descriptor_version_number()
<< " " << buf;
}
os << ")";
fe->file_attrs()->print (os, true);
os << ")";
buffer_index = fe->ent_marker()->stop;
}
}
if (_append_merge_parents) {
write_merge_parents ("Merge-Parents", _merge_parents, os);
write_merge_parents ("New-Merge-Parents", _new_merge_parents, os);
}
}
void ProjectDescriptor::write_merge_parents (const char* name,
MergeParentEntryPtrArray *entries,
ostream& os)
{
os << "(" << name;
if (entries) {
if (entries->length() > 0)
os << "\n";
for (int i = 0; i < entries->length(); i += 1) {
MergeParentEntry *entry = entries->index(i);
os << " (" << entry->selected_version << " ";
switch (entry->state) {
case MergeStateIncomplete:
os << "incomplete"; break;
case MergeStateParent:
os << "complete"; break;
case MergeStateIncompleteParent:
os << "incomplete-parent"; break;
}
int j = 0;
for (; j < entry->all_files.length(); j += 1) {
MergeParentFile* file = entry->all_files.index(j);
if (!(j % 2)) os << "\n ";
os << "(";
if (file->working)
print_protected (file->working, os);
else
os << "()";
os << " ";
if (file->common)
print_protected(file->common, os);
else
os << "()";
os << " ";
if (file->selected)
print_protected(file->selected, os);
else
os << "()";
os << " " << (char)file->action << ")";
if (!(j % 2)) os << " ";
}
if (j % 2) os << "\n ";
os << ")\n";
}
}
os << ")\n";
}
void ProjectDescriptor::delete_file (FileEntry* fe)
{
_deleted_markers->append(fe->ent_marker());
}
/* Quick Elim
*/
void ProjectDescriptor::read_quick_elim()
{
_quick_elim = NULL;
If_fail(_quick_elim << ::read_quick_elim(project_aux_path()))
return;
}
void ProjectDescriptor::quick_elim_update()
{
if(!_quick_elim)
return;
_quick_elim->update_file(project_aux_path());
}
void ProjectDescriptor::quick_elim_update_fe(FileEntry* fe)
{
if(!_quick_elim)
_quick_elim = new QuickElim;
_quick_elim->update(fe);
}
/* Match Fileent
*/
void ProjectDescriptor::populate_tables (void)
{
_file_name_table = new FileTable(pathname_hash, pathname_equal);
_file_desc_table = new FileTable(pathname_hash, pathname_equal);
_file_match_cache = new EntryTable;
foreach_fileent (fe_ptr, this) {
FileEntry *fe = *fe_ptr;
_file_name_table->insert (fe->working_path(), fe);
if (fe->file_type() == RealFile && fe->descriptor_name())
_file_desc_table->insert (fe->descriptor_name(), fe);
}
}
PrFileEntryPtrError ProjectDescriptor::match_fileent (FileEntry* fe)
{
ProjectDescriptor *project = fe->project();
/* fe is a FileEntry belonging to another project. Try
* to match it according to the standard rules (which are new
* in 1.2.0b8). */
if (!_file_name_table)
populate_tables();
FileEntry **cache_lu = _file_match_cache->lookup (fe);
if (cache_lu)
return *cache_lu;
FileEntry **name_lu = _file_name_table->lookup(fe->working_path());
if (fe->file_type() != RealFile || !fe->descriptor_name())
return name_lu ? *name_lu : NULL;
FileEntry **desc_lu = _file_desc_table->lookup(fe->descriptor_name());
if (name_lu && desc_lu && *desc_lu != *name_lu) {
prcsquery << "An attempt was made to match files between project version "
<< full_version() << " and " << project->full_version()
<< ". The result is ambiguous, due to (perhaps pathological) "
"naming and renaming of files. The file " << squote (fe->working_path())
<< " in version " << full_version() << " matches by name with "
<< squote ((*name_lu)->working_path()) << " and by file family with "
<< squote ((*desc_lu)->working_path()) << " in version "
<< project->full_version() << ". You are advised to abort and prevent this "
<< " from occuring, if possible. You may continue by selecting "
"one of the matches, or abort. "
<< force ("Using the file family match")
<< report ("Use the file family match")
<< allow_bang (_match_bang)
<< help (bad_match_help_string)
<< option ('n', "Continue matching by name")
<< defopt ('f', "Continue matching by file family")
<< query ("Select");
char c;
Return_if_fail (c << prcsquery.result());
if (c == 'n') {
_file_match_cache->insert (fe, *name_lu);
return *name_lu;
} else {
_file_match_cache->insert (fe, *desc_lu);
return *desc_lu;
}
} else if (desc_lu) {
return *desc_lu;
} else if (name_lu) {
return *name_lu;
} else
return (FileEntry*) 0;
}
bool ProjectDescriptor::can_add (FileEntry *fe)
{
if (!_file_name_table)
populate_tables();
FileEntry **name_lu = _file_name_table->lookup(fe->working_path());
if (name_lu)
return false;
if (fe->file_type() != RealFile || !fe->descriptor_name())
return true;
FileEntry **desc_lu = _file_desc_table->lookup(fe->descriptor_name());
return desc_lu == NULL;
}
/* Estring
*/
Estring::Estring (ProjectDescriptor* project, char* s, size_t off, size_t len, EstringType type)
:Dstring(), _offset(off), _len (len), _type(type)
{
project->register_estring(this, s[len]);
s[len] = 0;
_filled = len;
_vec = s;
}
Estring::Estring (ProjectDescriptor* project, size_t position)
:Dstring(), _offset(position), _len (0), _type(EsUnProtected)
{
project->register_estring(this, 0);
}
void Estring::modify()
{
if (_alloc > 0 || _filled == 0)
return;
_alloc = 1;
char* oldvec = _vec;
while (_alloc < _filled + 1) _alloc <<= 1;
_vec = NEWVEC0(char, _alloc);
strcpy (_vec, oldvec);
}
void Estring::append_protected (const char* string)
{
protect_path (string, this);
}
void Estring::append_string (const char* string)
{
append ("\"");
protect_string (string, this);
append ("\"");
}
void ProjectDescriptor::register_estring(Estring* estring, char missing)
{
if (!_all_estrings)
_all_estrings = new DstringPtrArray;
if (!_nullified_chars)
_nullified_chars = new Dstring;
_all_estrings->append (estring);
_nullified_chars->append (missing);
}
bool Estring::is_dstring() { return false; }
EstringType Estring::type() const { return _type; };
size_t Estring::offset() const { return _offset; }
size_t Estring::offset_length() const { return _len; }
/* Parser
*/
#include "prj-names.h"
#define ENTRIES sizeof(ProjectDescriptor::_pftable)/sizeof(ProjectDescriptor::_pftable[0])
const int ProjectDescriptor::_prj_entries = ENTRIES;
bool ProjectDescriptor::_prj_entry_found[ENTRIES];
static MemorySegment *prj_seg;
static int prj_read_current_offset;
FILE* prj_input = NULL;
extern "C"
int prjinput(void* buf, int max_size)
{
if (prj_input) {
int c;
If_fail (c << Err_fread( buf, max_size, prj_input))
return -1;
return c;
} else if (prj_seg) {
int length = prj_seg->length() - 1;
if (prj_read_current_offset + max_size > length)
max_size = length - prj_read_current_offset;
memcpy (buf, prj_seg->segment() + prj_read_current_offset, max_size);
prj_read_current_offset += max_size;
return max_size;
} else {
return 0;
}
}
PrVoidError ProjectDescriptor::parse_prj_file(FILE* infile)
{
PrjTokenType tok;
prj_seg = NULL;
while (prj_lex() != PrjEof) { }
_segment = prj_seg = new MemorySegment(true);
prj_read_current_offset = 0;
prj_lineno = 1;
prj_lex_cur_index = 0;
prj_lex_this_index = 0;
Return_if_fail (prj_seg->map_file (_prj_source_name, fileno(infile)));
_segment->append_segment("", 1); /* Added a null here for safety, going to
* have to get rid of it when writing. */
memset(_prj_entry_found, 0, sizeof(_prj_entry_found));
while (true) {
tok = prj_lex();
switch (tok) {
case PrjNull:
case PrjBadString:
case PrjString:
case PrjName:
case PrjClose:
return read_parse_bad_token (tok);
case PrjOpen:
Return_if_fail(read_list_checked(prj_lex_this_index));
break;
case PrjEof:
for (int i = 0; i < _prj_entries; i += 1)
if (_pftable[i].must_have && !_prj_entry_found[i])
pthrow prcserror << "Missing project file entry "
<< squote(_pftable[i].name) << dotendl;
_end_of_buffer_point = new Estring (this, prj_lex_cur_index);
return NoError;
}
}
}
PrConstSexpPtrError ProjectDescriptor::read_list_elt()
{
PrjTokenType tok = prj_lex();
switch (tok) {
case PrjEof:
case PrjNull:
case PrjBadString:
return read_parse_bad_token (tok);
case PrjString:
case PrjName:
return read_new_estring(tok);
case PrjClose:
return new Sexp (prj_lineno);
case PrjOpen:
break;
}
return read_list(prj_lex_this_index);
}
PrConstSexpPtrError ProjectDescriptor::read_new_estring(PrjTokenType type)
{
int start_index = prj_lex_this_index + (type == PrjString);
int stop_index = prj_lex_cur_index - (type == PrjString);
Estring *estring = new Estring(this,
_segment->wr_segment() + start_index,
start_index,
stop_index - start_index,
type == PrjName ? EsNameLiteral : EsStringLiteral);
if (strchr (estring->cast(), '\\')) {
/* All names/strings are deslashified when read. */
estring->truncate(0);
correct_path(_segment->wr_segment() + start_index, estring);
}
return new Sexp (estring, prj_lineno);
}
PrVoidError ProjectDescriptor::read_parse_bad_token(PrjTokenType tok)
{
switch (tok) {
case PrjEof:
pthrow prcserror << fileline(_prj_source_name, prj_lineno)
<< "Unexpected EOF" << dotendl;
case PrjNull:
pthrow prcserror << fileline(_prj_source_name, prj_lineno)
<< "Illegal null character" << dotendl;
case PrjBadString:
pthrow prcserror << fileline(_prj_source_name, prj_lineno)
<< "Unterminated string constant" << dotendl;
case PrjString:
case PrjName:
pthrow prcserror << fileline(_prj_source_name, prj_lineno)
<< "Unexpected token: " << squote(prj_lex_val) << dotendl;
case PrjClose:
pthrow prcserror << fileline(_prj_source_name, prj_lineno)
<< "Unexpected ')'" << dotendl;
case PrjOpen:
pthrow prcserror << fileline(_prj_source_name, prj_lineno)
<< "Unexpected '('" << dotendl;
}
return NoError;
}
PrConstSexpPtrError ProjectDescriptor::read_list_token(PrjTokenType tok, int start_index)
{
const Sexp *s1, *s2;
Sexp *s;
int start_line = prj_lineno;
switch (tok) {
case PrjEof: case PrjNull: case PrjBadString:
return read_parse_bad_token (tok);
case PrjString:
case PrjName:
Return_if_fail(s2 << read_new_estring (tok));
Return_if_fail(s1 << read_list(start_index));
s = new Sexp(s2, s1, prj_lineno);
s->set_start (start_index);
s->set_end (prj_lex_cur_index);
return s;
case PrjClose:
s = new Sexp (prj_lineno);
s->set_start (start_index);
s->set_end (prj_lex_cur_index);
return s;
case PrjOpen:
break;
}
Return_if_fail(s1 << read_list(prj_lex_this_index));
Return_if_fail(s2 << read_list(start_index));
s = new Sexp(s1, s2, start_line);
s->set_start (start_index);
s->set_end (prj_lex_cur_index);
return s;
}
PrConstSexpPtrError ProjectDescriptor::read_list(int offset)
{
return read_list_token (prj_lex(), offset);
}
PrVoidError ProjectDescriptor::read_list_checked(size_t offset)
{
const PrjFields* pf;
PrjTokenType tok;
const Sexp* s;
int h;
tok = prj_lex();
if (tok != PrjName)
pthrow prcserror << fileline(_prj_source_name, prj_lineno)
<< "Expecting label at head of list" << dotendl;
pf = prj_lookup_func(prj_lex_val, prj_lex_len);
h = prj_lookup_hash(prj_lex_val, prj_lex_len);
if (!pf)
pthrow prcserror << "Unexpected label " << squote (prj_lex_val) << dotendl;
if (_prj_entry_found[h] && h != prj_lookup_hash ("Files", strlen ("Files")))
pthrow prcserror << fileline(_prj_source_name, prj_lineno)
<< "Duplicate project file entry" << dotendl;
_prj_entry_found[h] = true;
if (pf->parse_func) {
Return_if_fail (s << read_list_token (tok, offset));
/* I finally found a use for the ->* operator, fear it. */
/*Return_if_fail( (this ->* (pf->parse_func)) (s) );*/
Return_if_fail( (this ->* (pf->parse_func)) (s) );
delete s;
} else {
Return_if_fail( (this ->* (pf->read_func)) (offset) );
}
return NoError;
}
syntax highlighted by Code2HTML, v. 0.9.1