/* -*-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: vc.cc 1.13.1.1.1.11.1.12.1.11.1.6.1.20 Wed, 06 Feb 2002 20:57:16 -0800 jmacd $
*/
extern "C" {
#include <signal.h>
}
#include "prcs.h"
#include "hash.h"
#include "vc.h"
#include "syscmd.h"
#include "system.h"
#include "misc.h"
#include "memseg.h"
EXTERN char* rcs_text; /* declared in vc.l */
EXTERN int VC_get_token(FILE* is);
EXTERN int VC_init_seg_get_c(char* seg, size_t len);
static bool VC_expect_token(FILE* stream, int type, Dstring* result);
static PrVoidError fix_version_data(const ProjectVersionDataPtrArray *pvda,
const DstringPtrArray *old_fmt_parents);
extern "C" void vc_lex_fatal_error(const char* msg)
{
prcserror << msg;
if (errno != 0)
prcserror << perror;
else
prcserror << dotendl;
kill (getpid(), SIGINT);
}
void VC_init_seg_get(MemorySegment* seg)
{
seg->append_segment("\0\0", 2);
VC_init_seg_get_c((char*)seg->segment(), seg->length());
}
/*
* VC_checkin --
*
* Buffers checkins of working files into RCS versionfiles. It
* first calls upon RCS to lock the parent version. Currently,
* one fork to RCS is required for each file. This could possibly
* be replaced in the future by either some analysis of the time
* stamp on the file (and assume nothing has been checked in since
* the last was checked in) or by parsing the RCS file according
* to rcsfile(5). This is the simplest and most transparent way
* of doing this, however, it only relies on RCS's external
* interface.
*
* when VC_checkin is called with all arguments except the log
* NULL, the actual checkin takes place. Also, if the argument
* size approaches sysconf(_SC_ARG_MAX), the buffer will be
* flushed. The checkin unlocks the RCS file.
* Results:
* true if everything is going okay. false if an error has
* occured. the new RCS version assigned to newfile is returned
* in the Dstring *parentversion. If parent version was empty,
* then checkin will fill RCS version 1.1.
* Side Effects:
* if sucessful, the RCS files will be modified.
* Parameters:
* newfile -- path of the working file.
* parentversion -- return buffer for new version number
* versionfile -- path of the RCS version file.
* log -- a log message
*/
typedef void (* CheckinHook)(void* data, const char* new_version);
struct CheckinData {
CheckinData(const char* file0,
const char* ver0,
const char* log0,
void* data0,
CheckinHook notify_func0)
:file(file0), ver(ver0), log(log0), data(data0),
notify_func(notify_func0) { }
const char* file;
const char* ver;
const char* log;
void* data;
CheckinHook notify_func;
};
struct CheckinData2 {
CheckinData2 (VoidPtrList* l0, CheckinHook notify_func0, int n0)
:l(l0), notify_func(notify_func0), n(n0) { }
VoidPtrList *l;
CheckinHook notify_func;
int n;
};
PrVoidError VC_checkin_stage2(MemorySegment*, MemorySegment*, int exit_val, void* vdata);
PrVoidError VC_checkin_stage3(MemorySegment*, MemorySegment*, int exit_val, void* vdata);
PrVoidError VC_checkin(const char* newfile,
const char* parentversion,
const char* versionfile,
const char* log,
void* data,
CheckinHook notify_func)
{
CheckinData* c_data = new CheckinData(newfile, versionfile,
log, data, notify_func);
if( newfile != NULL) {
Dstring ver;
ArgList *argl;
Return_if_fail(argl << rcs_command.new_arg_list());
/* if the file being checked in has a parent version, then
* instruct RCS to pick a good new version number by locking the
* parent version
*/
if (parentversion != NULL && parentversion[0]) {
ver.append("-l");
ver.append(parentversion);
argl->append("-q");
argl->append("-T"); /* preserve time stamp */
argl->append("-u");
argl->append("-M");
argl->append(ver.cast());
argl->append(versionfile);
} else if (parentversion != NULL && !parentversion[0]) {
/*
* if the version is empty, assume the file will be the *
* initial version. We still must try to lock the version
* in the case the file is being checked in by a different
* owner and no locks are set (eg. the project file, which
* doesn't track RCS versions). To prove this:
* user1% rcs -i -U foo,v
* # creates initial version without strict locking
* user2% co foo
* user2% chmod +w foo
* # essentially what PRCS does, since it checks out on stdout
* # modify foo
* user2% ci foo
* # no locks set by user2, fail
* user1% ci foo
* # since user1 owns it, it succeeds, I guess.
*
* The point is, you won't see why this is neccesary until
* you try using two users.
*/
argl->append("-q");
argl->append("-T"); /* preserve time stamp */
argl->append("-u");
argl->append("-M");
argl->append("-l");
argl->append(versionfile);
}
Return_if_fail(rcs_command.open_delayed (VC_checkin_stage2, c_data, false, false));
} else {
Return_if_fail(DelayedJob::delay_wait(true));
DEBUG ("starting ci processes");
Return_if_fail(VC_checkin_stage2(NULL, NULL, 0, c_data));
Return_if_fail(DelayedJob::delay_wait(true));
}
return NoError;
}
PrVoidError VC_checkin_stage2(MemorySegment*, MemorySegment*, int exit_val, void* v_data)
{
static VoidPtrList *accumulate = NULL;
static ArgList *args = NULL;
CheckinData *c_data = (CheckinData*) v_data;
const char* newfile = c_data->file;
const char* log = c_data->log;
const char* versionfile = c_data->ver;
void* data = c_data->data;
CheckinHook func = c_data->notify_func;
delete c_data;
if (exit_val != 0)
pthrow prcserror << "RCS failed on file " << versionfile << dotendl;;
if (!args && !accumulate && !newfile)
return NoError;
if (!args)
args = new ArgList;
if (newfile == NULL) {
ArgList* ci_args;
int file_count = args->length() / 2;
int ratio = file_count / option_jobs;
int files_per_proc = 4 > ratio ? 4 : ratio;
int max_arg_size = sysconf(_SC_ARG_MAX) - (4 * MAXPATHLEN);
#ifdef __BEOS__
/* ci sometimes freezes when given more than one file,
* and is remarkably slow when given more then 32 files in
* any case. *sigh*
*/
files_per_proc = 1;
#endif
DEBUG ("using " << files_per_proc << " files per child");
VoidPtrList* acc_end;
accumulate = accumulate->nreverse();
Dstring logmes;
Dstring date;
date.append("-d");
date.append(get_utc_time());
if (log) {
logmes.append("-m");
logmes.append(log);
}
for (int j = 0; j < file_count;) {
int arg_list_size = 0;
int this_round = 0;
acc_end = accumulate;
Return_if_fail(ci_args << ci_command.new_arg_list());
if (log)
ci_args->append(logmes.cast());
ci_args->append(date.cast());
ci_args->append("-T");
ci_args->append("-f");
/*if (keep)
ci_args->append("-u");*/
for (; j < file_count &&
arg_list_size < max_arg_size &&
this_round < files_per_proc; j += 1, this_round += 1) {
ci_args->append(args->index(2*j));
ci_args->append(args->index(2*j+1));
arg_list_size += 2;
arg_list_size += strlen(args->index(2*j));
arg_list_size += strlen(args->index(2*j+1));
acc_end = acc_end->tail();
}
DEBUG("ci with " << this_round << " jobs");
Return_if_fail(ci_command.open_delayed(VC_checkin_stage3,
new CheckinData2(accumulate, func, this_round),
false, true));
accumulate = acc_end;
}
delete args;
args = new ArgList;
accumulate = NULL;
} else {
accumulate = new VoidPtrList(data, accumulate);
args->append(newfile);
args->append(versionfile);
}
return NoError;
}
PrVoidError VC_checkin_stage3(MemorySegment*, MemorySegment* err_seg, int exit_val, void* v_data)
{
CheckinData2* data = (CheckinData2*) v_data;
int n = data->n;
CheckinHook func = data->notify_func;
VoidPtrList *l = data->l;
VoidPtrList *t;
delete data;
if (exit_val != 0) {
cerr.write(err_seg->segment(), err_seg->length());
pthrow prcserror << "RCS ci failed on batch command" << dotendl;
}
VC_init_seg_get(err_seg);
for (; n > 0; n -= 1) {
int vclex = VC_get_token(NULL);
if(vclex != NewRevision &&
vclex != InitialRevision)
pthrow prcserror << "Error scanning RCS ci output" << dotendl;
if (l->head() != NULL)
(* func) (l->head(), rcs_text);
t = l;
l = l->tail();
delete t;
}
return NoError;
}
/*
* VC_register --
*
* Used to create an initial RCS versionfile with no versions. This
* avoids a condition in VC_checkin where RCS would be confused by
* a missing file.
* Results:
* If sucessful, file has been registered. Files are not actually
* created via a fork/exec to rcs until VC_register is called with
* NULL as its filename. It will not register the same file twice
* even if the buffered files have not been created. That is, two
* sucessive calls to VC_register with the same name will fail until
* VC_register(NULL) has been called. After that, it will succeed
* again. It is someone elses duty to check that the file doesn't
* already exist.
*
* Should the buffer size approach sysconf(_SC_ARG_MAX), it will
* automatically flush the buffer.
* Side Effects:
* when VC_register(NULL) gets called, files will be created.
* Parameters:
* file names a file, or flushes the buffer if NULL.
*/
#define REGISTER_COUNT 64 /* I determined experimentally that this
* is reasonably optimal for both single
* and multi-process checkins. */
PrVoidError VC_register_callback (MemorySegment*,
MemorySegment*,
int exit_val,
void* data)
{
bool *n_success = (bool*) data;
if (exit_val != 0)
*n_success = true;
return NoError;
}
PrVoidError VC_register(const char* file)
{
static ArgList *accumulate = NULL;
static bool n_success;
if ((accumulate == NULL || accumulate->length() == 0) && file == NULL)
return NoError;
if (accumulate == NULL)
accumulate = new ArgList;
if (file == NULL || accumulate->length() == REGISTER_COUNT) {
ArgList *argl;
Return_if_fail(argl << rcs_command.new_arg_list());
argl->append("-i"); /* RCS init */
argl->append("-t/dev/null"); /* No initial description message. */
argl->append("-U"); /* unset strict locking */
argl->append("-q"); /* be quiet */
for(int i = 0; i < accumulate->length(); i += 1)
argl->append(accumulate->index(i));
Return_if_fail(rcs_command.open_delayed(VC_register_callback,
&n_success,
false,
false));
accumulate->truncate(0);
}
if(file != NULL) {
accumulate->append(file);
} else {
Return_if_fail(DelayedJob::delay_wait(true));
bool tmp = n_success;
n_success = false;
if (tmp)
pthrow prcserror << "RCS -i failed on batch init" << dotendl;
}
return NoError;
}
/*
* VC_expect_token --
*
* Used when a certain token type is expected from the token stream.
* Results:
* Returns true when the expected token type is found, false otherwise.
* the external variable rcs_text will also contain the token text.
* Side Effects:
* One token is read from the stream via a call to VC_get_token.
* Parameters:
* stream is the argument passed to VC_get_token, possibly NULL to
* continue reading the last stream passed. type is one of the
* #defined types in include/vc.h. result gets the value of the
* token text if succesful, if non-null.
*/
static bool VC_expect_token(FILE* stream, int type, Dstring* result)
{
if(type != VC_get_token(stream))
return false;
else if(result)
result->assign(rcs_text);
return true;
}
/*
* VC_checkout_stream --
*
* Retrieves version from revisionfile, into a stream.
* Results:
* returns a FILE* opened for reading or NULL upon failure.
* Side Effects:
* a file descriptor is open. VC_close_checkout_stream should
* be called.
* Parameters:
* versionfile names the pathname of an RCS file. version is
* retrieved.
*/
PrCFilePtrError VC_checkout_stream(const char* version,
const char* revisionfile)
{
ArgList *argl;
Dstring ver("-p");
Return_if_fail(argl << co_command.new_arg_list());
ver.append(version);
argl->append("-ko");
argl->append(ver);
argl->append(revisionfile);
If_fail(co_command.open(true, true))
pthrow prcserror << "Failed checking out version " << version
<< " from file " << squote(revisionfile) << dotendl;
return co_command.standard_out();
}
/*
* VC_close_checkout_stream --
*
* cleans up after a VC_checkout_stream command and checks that RCS
* did not report any errors.
* Results:
* true if everything went okay.
* Side effects:
* FILE *f will be closed.
* Parameters:
* RCS versionfile file and version.
*/
#ifdef NDEBUG
#define UNUSED(x)
#else
#define UNUSED(x) x
#endif
PrVoidError VC_close_checkout_stream(FILE* UNUSED(f), const char* version, const char* file)
{
if(!VC_expect_token(co_command.standard_err(), RlogVersion, NULL) ||
strcmp(rcs_text, version) != 0)
pthrow prcserror << "Consistency check failed checking out version "
<< version << " from file " << squote(file) << dotendl;
ASSERT(f == co_command.standard_out(), "no way, man!");
Return_if_fail_if_ne(co_command.close(), 0)
pthrow prcserror << "co exited with non-zero status" << dotendl;
return NoError;
}
/*
* VC_checkout_file --
*
* checks out a file and places it in newfile.
* Results:
* true if everything went okay.
* Side effects:
* None.
* Parameters:
* RCS versionfile file and version. File is placed in newfile.
*/
PrVoidError VC_checkout_file(const char* newfile,
const char* version,
const char* revisionfile)
{
FILE *infile, *outfile;
Return_if_fail(infile << VC_checkout_stream(version, revisionfile));
If_fail(outfile << Err_fopen(newfile, "w"))
pthrow prcserror << "Can't open file " << squote(newfile)
<< " for writing" << perror;
Return_if_fail(write_file(infile, outfile));
Return_if_fail(VC_close_checkout_stream(infile, version, revisionfile));
If_fail(Err_fclose(outfile))
pthrow prcserror << "Write failed on file " << squote(newfile) << perror;
return NoError;
}
/*
* VC_get_project_version_data --
*
* Retrieves all the log information for a PRCS project file. (which
* is used to map PRCS versions to RCS versions) from an RCS versionfile.
* Results:
* If return is successful, return value is a heap allocated array of
* ProjectVersionData.
* Side Effects:
* Memory is allocated for the return value.
* Parameters:
* versionfile names the pathname of an RCS file.
*/
PrProjectVersionDataPtrArrayPtrError
VC_get_project_version_data(const char* versionfile)
{
ArgList *argl;
int rev_count, i;
Return_if_fail(argl << rlog_command.new_arg_list());
argl->append(versionfile);
Return_if_fail(rlog_command.open(true, false));
if(VC_get_token(rlog_command.standard_out()) == RlogTotalRevisions)
rev_count = atoi(rcs_text);
else
pthrow prcserror << "Unrecognized output from RCS rlog command on RCS file "
<< squote(versionfile) << dotendl;
ProjectVersionDataPtrArray* pvda = new ProjectVersionDataPtrArray (rev_count);
if(rev_count == 0)
return pvda;
/* This crap is here for backward compatiblity. The new formats
* require knowing the index of the parent, not the version name,
* for simplicity, but the versions arrive out of order, so the
* parent names or NULL are stored until all versions arrive. */
DstringPtrArray* old_fmt_parents = new DstringPtrArray (rev_count);
DstringPtrArray* old_fmt_descends = new DstringPtrArray (rev_count);
pvda->expand (rev_count, (ProjectVersionData *) 0);
old_fmt_parents->expand (rev_count, (Dstring *) 0);
old_fmt_descends->expand (rev_count, (Dstring *) 0);
if(VC_get_token(NULL) != RlogVersion)
goto error;
for(i = 0; i < rev_count; i += 1) {
RcsToken vclex;
Dstring *par_ver_maj, *desc_ver_maj;
ProjectVersionData* pvd = new ProjectVersionData(rev_count - 1 - i);
pvd->rcs_version(p_strdup(rcs_text));
pvda->index(rev_count - 1 - i, pvd);
while(true) {
vclex = (RcsToken)VC_get_token(NULL);
if(vclex == RlogVersion || vclex == NoTokenMatch)
break;
switch(vclex) {
case RlogLines:
/* Don't care for the PVD lines */
break;
case RlogDate:
pvd->date(timestr_to_time_t(rcs_text));
break;
case RlogAuthor:
pvd->author(p_strdup(rcs_text));
break;
case PrcsMajorVersion:
pvd->prcs_major(p_strdup(rcs_text));
break;
case PrcsMinorVersion:
pvd->prcs_minor(p_strdup(rcs_text));
break;
case PrcsDescendsMajorVersion:
desc_ver_maj = new Dstring (rcs_text);
vclex = (RcsToken)VC_get_token(NULL);
if (vclex != PrcsDescendsMinorVersion)
goto error;
if (strcmp (rcs_text, "0") == 0) {
/* Its a empty parent version, no parents. */
delete desc_ver_maj;
break;
}
desc_ver_maj->append ('.');
desc_ver_maj->append (rcs_text);
old_fmt_descends->index(rev_count - 1 - i, desc_ver_maj);
break;
case PrcsParentMajorVersion:
par_ver_maj = new Dstring (rcs_text);
vclex = (RcsToken)VC_get_token(NULL);
if (vclex != PrcsParentMinorVersion)
goto error;
if (strcmp (rcs_text, "0") == 0) {
/* Its a empty parent version, no parents. */
delete par_ver_maj;
break;
}
par_ver_maj->append ('.');
par_ver_maj->append (rcs_text);
old_fmt_parents->index(rev_count - 1 - i, par_ver_maj);
break;
case PrcsVersionDeleted:
pvd->deleted(true);
break;
case PrcsParents:
if (rcs_text[0]) {
const char* pis = strtok (rcs_text, ":");
int pi = atoi (pis);
if (!pis[0] || pi < 0 || pi >= (rev_count - 1 - i))
pthrow prcserror << "Invalid parent version index " << squote (rcs_text)
<< " in " << squote (versionfile)
<< " RCS version 1." << (rev_count - i) << dotendl;
pvd->new_parent(pi);
while ((pis = strtok (NULL, ":")) != NULL) {
pi = atoi (pis);
if (!pis[0] || pi < 0 || pi >= (rev_count - 1 - i))
pthrow prcserror << "Invalid parent version index " << squote (rcs_text)
<< " in " << squote (versionfile)
<< " RCS version 1." << (rev_count - i) << dotendl;
pvd->new_parent(pi);
}
}
break;
case NoTokenMatch:
case RlogTotalRevisions:
case RlogVersion:
case PrevRevision:
case NewRevision:
case InitialRevision:
case PrcsParentMinorVersion:
case PrcsDescendsMinorVersion:
case RcsAbort:
goto error;
}
}
if (!pvd->OK())
pthrow prcserror << "Incomplete version log information in RCS file "
<< squote(versionfile)
<< " version 1." << (rev_count - i) << dotendl;
}
Return_if_fail_if_ne(rlog_command.close(), 0)
pthrow prcserror << "RCS rlog command returned non-zero status on RCS file "
<< squote(versionfile) << dotendl;
Return_if_fail(fix_version_data(pvda, old_fmt_parents));
Return_if_fail(fix_version_data(pvda, old_fmt_descends));
foreach (ds_ptr, old_fmt_parents, DstringPtrArray::ArrayIterator) {
if (*ds_ptr) {
delete *ds_ptr;
}
}
delete old_fmt_parents;
return pvda;
error:
delete old_fmt_parents;
delete pvda;
pthrow prcserror << "Error scanning RCS rlog output on RCS file "
<< squote(versionfile) << dotendl;
}
/* This is here mostly for backward compatibility. */
static PrVoidError
fix_version_data(const ProjectVersionDataPtrArray *pvda,
const DstringPtrArray *old_fmt_parents)
{
/* Use a slow (O(n^2) in the number of versions) search here, it
* avoids a lot of complexity, C++ hassle, and is only required
* for versions prior to 1.2 during rebuild. In fact, avert your
* eyes, if I could start over, I would. */
for (int i = 0; i < pvda->length(); i += 1) {
if (old_fmt_parents->index(i)) {
for (int j = 0; j < i; j += 1) {
const char* par_name = *old_fmt_parents->index(i);
if (strcmp (pvda->index(j)->prcs_minor(), minor_version_of (par_name)) == 0 &&
strcmp (pvda->index(j)->prcs_major(), major_version_of (par_name)) == 0) {
pvda->index(i)->new_parent(j);
break;
}
}
}
}
return NoError;
}
/*
* VC_get_version_data --
*
* Retrieves all the log information from an RCS file.
* Results:
* If return is successful, return value is a heap allocated array of
* RcsVersionData.
* Side Effects:
* Memory is allocated for the return value.
* Parameters:
* versionfile names the pathname of an RCS file.
*/
PrRcsDeltaPtrError
VC_get_version_data(const char* versionfile)
{
ArgList *argl;
int rev_count, i;
Return_if_fail(argl << rlog_command.new_arg_list());
argl->append(versionfile);
Return_if_fail(rlog_command.open(true, false));
if(VC_get_token(rlog_command.standard_out()) == RlogTotalRevisions)
rev_count = atoi(rcs_text);
else
pthrow prcserror << "Unrecognized output from RCS rlog command on RCS file "
<< squote(versionfile) << dotendl;
RcsDelta* pvda = new RcsDelta(NULL);
if(rev_count == 0)
return pvda;
if(VC_get_token(NULL) != RlogVersion)
goto error;
for(i = 0; i < rev_count; i += 1) {
RcsToken vclex;
RcsVersionData* pvd = new RcsVersionData;
pvd->rcs_version(p_strdup(rcs_text));
pvda->insert(pvd->rcs_version(), pvd);
while(true) {
vclex = (RcsToken)VC_get_token(NULL);
if(vclex == RlogVersion || vclex == NoTokenMatch)
break;
switch(vclex) {
case RlogDate:
pvd->date(timestr_to_time_t(rcs_text));
break;
case RlogAuthor:
pvd->author(p_strdup(rcs_text));
break;
case RlogLines:
pvd->set_lines (rcs_text);
break;
case PrcsMajorVersion:
case PrcsMinorVersion:
case PrcsParentMajorVersion:
case PrcsParentMinorVersion:
case PrcsDescendsMajorVersion:
case PrcsDescendsMinorVersion:
/* These are okay to make really really ancient repositories
* work, probably only the prcs repository. */
break;
case PrcsVersionDeleted:
case PrcsParents:
case NoTokenMatch:
case RlogTotalRevisions:
case RlogVersion:
case PrevRevision:
case NewRevision:
case InitialRevision:
case RcsAbort:
goto error;
}
}
if(!pvd->OK())
pthrow prcserror << "Incomplete version log information in RCS file "
<< squote(versionfile) << dotendl;
}
Return_if_fail_if_ne(rlog_command.close(), 0)
pthrow prcserror << "RCS rlog command returned non-zero status on RCS file "
<< squote(versionfile) << dotendl;
return pvda;
error:
pthrow prcserror << "Error scanning RCS rlog output on RCS file "
<< squote(versionfile) << dotendl;
}
PrVoidError
VC_get_one_version_data (const char* versionfile, const char *versionnum, RcsVersionData *rvd)
{
ArgList *argl;
RcsToken vclex;
Return_if_fail(argl << rlog_command.new_arg_list());
Dstring vstr;
vstr.append ("-r");
vstr.append (versionnum);
argl->append(vstr.cast ());
argl->append(versionfile);
Return_if_fail(rlog_command.open(true, false));
if (VC_get_token(rlog_command.standard_out()) != RlogTotalRevisions) {
pthrow prcserror << "Unrecognized output from RCS rlog command on RCS file "
<< squote(versionfile) << dotendl;
}
if (VC_get_token(NULL) != RlogVersion || strcmp(versionnum, rcs_text) != 0) {
goto error;
}
rvd->rcs_version(p_strdup(rcs_text));
while(true) {
vclex = (RcsToken)VC_get_token(NULL);
if(vclex == RlogVersion)
goto error;
if (vclex == NoTokenMatch)
break;
switch(vclex) {
case RlogDate:
rvd->date(timestr_to_time_t(rcs_text));
break;
case RlogAuthor:
rvd->author(p_strdup(rcs_text));
break;
case RlogLines:
rvd->set_lines (rcs_text);
break;
case PrcsMajorVersion:
case PrcsMinorVersion:
case PrcsParentMajorVersion:
case PrcsParentMinorVersion:
case PrcsDescendsMajorVersion:
case PrcsDescendsMinorVersion:
/* These are okay to make really really ancient repositories
* work, probably only the prcs repository. */
break;
case PrcsVersionDeleted:
case PrcsParents:
case NoTokenMatch:
case RlogTotalRevisions:
case RlogVersion:
case PrevRevision:
case NewRevision:
case InitialRevision:
case RcsAbort:
goto error;
}
}
if(!rvd->OK()) {
pthrow prcserror << "Incomplete version log information in RCS file "
<< squote(versionfile) << dotendl;
}
Return_if_fail_if_ne(rlog_command.close(), 0) {
pthrow prcserror << "RCS rlog command returned non-zero status on RCS file "
<< squote(versionfile) << dotendl;
}
return NoError;
error:
pthrow prcserror << "Error scanning RCS rlog output on RCS file "
<< squote(versionfile) << dotendl;
}
PrIntError
VC_get_version_count(const char* versionfile)
{
ArgList *argl;
int rev_count;
Return_if_fail(argl << rlog_command.new_arg_list());
argl->append("-h");
argl->append(versionfile);
Return_if_fail(rlog_command.open(true, false));
if(VC_get_token(rlog_command.standard_out()) == RlogTotalRevisions) {
rev_count = atoi(rcs_text);
} else {
pthrow prcserror << "Unrecognized output from RCS 'rlog -h' command on RCS file "
<< squote(versionfile) << dotendl;
}
Return_if_fail_if_ne(rlog_command.close(), 0) {
pthrow prcserror << "RCS rlog command returned non-zero status on RCS file "
<< squote(versionfile) << dotendl;
}
return rev_count;
}
PrBoolError VC_delete_version(const char* version,
const char* revisionfile)
{
ArgList* args;
Dstring outdate;
Return_if_fail(args << rcs_command.new_arg_list());
outdate.append("-o");
outdate.append(version);
args->append("-q");
args->append(outdate);
args->append(revisionfile);
Return_if_fail(rcs_command.open(true, true));
Return_if_fail_if_ne(rcs_command.close(), 0)
return false;
return true;
}
PrVoidError VC_set_log(const char* new_log_text,
const char* version,
const char* revisionfile)
{
Dstring log;
ArgList* args;
log.append("-m");
log.append(version);
log.append(":");
log.append(new_log_text);
Return_if_fail(args << rcs_command.new_arg_list());
args->append(log);
args->append(revisionfile);
Return_if_fail(rcs_command.open(true, true));
Return_if_fail_if_ne(rcs_command.close(), 0) {
pthrow prcserror << "RCS exited abnormally while updating log" << dotendl;
}
return NoError;
}
/* RcsDelta */
void RcsDelta::insert(const char* index, RcsVersionData* data)
{
insertion_count += 1;
if(index[0] == '\0') {
rcs_data = data;
return;
}
char* end_ptr;
int delta_num = (int)strtol(index, &end_ptr, 10);
if(!delta_array)
delta_array = new RcsDeltaPtrArray;
delta_array->expand(delta_num + 1, (RcsDelta*)0);
if(delta_array->index(delta_num) == NULL)
delta_array->index(delta_num, new RcsDelta(this));
if(*end_ptr)
delta_array->index(delta_num)->insert(end_ptr + 1, data);
else
delta_array->index(delta_num)->insert(end_ptr, data);
}
RcsVersionData* RcsDelta::lookup(const char* index)
{
char* end_ptr;
int delta_num = (int)strtol(index, &end_ptr, 10);
if(!index[0])
return rcs_data;
else if(!delta_array || delta_array->length() <= delta_num)
return NULL;
else if(delta_array->index(delta_num)) {
if(*end_ptr)
return delta_array->index(delta_num)->lookup(end_ptr + 1);
else
return delta_array->index(delta_num)->lookup(end_ptr);
} else
return NULL;
}
void RcsDelta::remove(const char* index)
{
insertion_count -= 1;
char* end_ptr;
int delta_num = (int)strtol(index, &end_ptr, 10);
if(!index[0])
rcs_data = NULL;
else if(!delta_array || delta_array->length() <= delta_num)
/* its not here? */
return;
else if(delta_array->index(delta_num)) {
if(*end_ptr)
delta_array->index(delta_num)->remove(end_ptr + 1);
else
delta_array->index(delta_num)->remove(end_ptr);
}
/* else its not here whats up with that */
}
/* This does an iterative table walk, you dont have to read it */
void RcsDelta::DeltaIterator::next()
{
static int array_length; /* this doesnt need to eat stack space */
_index += 1;
if(!_current->delta_array) {
array_length = 0;
} else {
array_length = _current->delta_array->length();
}
/* Its a leaf, 0 is the length of the non-existant array,
* the data of a particular node is returned after the
* children have been passed over. */
if(_index == array_length) {
_current_data = _current->rcs_data;
/* _current_data may be NULL if this version was just deleted,
* if so, call next() again. */
if(!_current_data)
next();
return;
} else if(_index < array_length) {
if(_current->delta_array->index(_index)) {
_current->my_iter_index = _index;
_current = _current->delta_array->index(_index);
_index = -1;
}
next();
return;
} else {
if(!_current->parent) {
/* this is the root, so we have covered all of them. */
_finished = true;
return;
} else {
/* Restore the parents state */
_current = _current->parent;
_index = _current->my_iter_index;
next();
return;
}
}
}
RcsDelta::~RcsDelta()
{
if(delta_array) {
foreach(child, delta_array, RcsDeltaPtrArray::ArrayIterator) {
if(*child) {
delete *child;
}
}
delete delta_array;
}
if(rcs_data) {
delete rcs_data;
}
}
RcsDelta::RcsDelta(RcsDelta* parent0)
:rcs_data(NULL), delta_array(NULL),
insertion_count(0),
parent(parent0), my_iter_index(-1) { }
int RcsDelta::count() const { return insertion_count; }
RcsDelta::DeltaIterator::DeltaIterator(RcsDelta* d0) :_current(d0), _finished(false), _index(-1) { next(); }
RcsVersionData* RcsDelta::DeltaIterator::operator*() const { return _current_data; }
bool RcsDelta::DeltaIterator::finished() const { return _finished; }
/* RcsVersionData */
RcsVersionData::RcsVersionData()
:_date(0),
_author(NULL),
_rcs_version(NULL),
_plus_lines (0),
_minus_lines (0),
_gc_mark(NotReferenced) { }
RcsVersionData::~RcsVersionData() { }
void RcsVersionData::set_lines(const char *lines)
{
ASSERT (lines[0] == '+', "the format is...");
char *endp, *endp2;
long pl = strtol (lines+1, &endp, 10);
ASSERT (strncmp (endp, " -", 2) == 0, "the format is...");
long ml = strtol (endp + 2, & endp2, 10);
ASSERT (endp2[0] == '\n', "the format is...");
_plus_lines = pl;
_minus_lines = ml;
//prcsoutput << "set lines: " << lines << " plus " << pl << " minus " << ml << prcsendl;
}
void RcsVersionData::set_plus_lines(int pls)
{
_plus_lines = pls;
}
void RcsVersionData::set_minus_lines(int mls)
{
_minus_lines = mls;
}
void RcsVersionData::set_plus_lines(const char *pls)
{
char *endp;
long pl = strtol (pls, &endp, 10);
ASSERT (endp[0] == 0, "bad format");
_plus_lines = pl;
}
void RcsVersionData::set_minus_lines(const char *mls)
{
char *endp;
long ml = strtol (mls, &endp, 10);
ASSERT (endp[0] == 0, "bad format");
_minus_lines = ml;
}
time_t RcsVersionData::date() const { return _date; }
int RcsVersionData::length() const { return _length; }
const char* RcsVersionData::author() const { return _author; }
const char* RcsVersionData::rcs_version() const { return _rcs_version; }
const char* RcsVersionData::unkeyed_checksum() const { return _unkeyed_checksum; }
int RcsVersionData::plus_lines() const { return _plus_lines; }
int RcsVersionData::minus_lines() const { return _minus_lines; }
RcsVersionData::GCMark RcsVersionData::referenced() const { return _gc_mark; }
void RcsVersionData::date(time_t t0) { _date = t0; }
void RcsVersionData::length(int l0) { _length = l0; }
void RcsVersionData::author(const char* a0) { _author = a0; }
void RcsVersionData::rcs_version(const char* rv0) { _rcs_version = rv0; }
void RcsVersionData::unkeyed_checksum(const char* ck0) { memcpy(_unkeyed_checksum, ck0, 16); }
void RcsVersionData::reference(GCMark gc0) { _gc_mark = gc0; }
bool RcsVersionData::OK() const { return _date > 0 && _author && _rcs_version; }
/* ProjectVersionData */
ProjectVersionData::ProjectVersionData(int index)
:RcsVersionData(),
_prcs_major(NULL), _prcs_minor(NULL),
_prcs_minor_int(-1), _index(index),
_deleted(false) { }
ProjectVersionData::~ProjectVersionData() { }
void ProjectVersionData::clear_flags() { _flag1 = false; _flag2 = false; }
bool ProjectVersionData::flag1(bool n) { bool o = _flag1; _flag1 = n; return o; }
bool ProjectVersionData::flag2(bool n) { bool o = _flag2; _flag2 = n; return o; }
bool ProjectVersionData::flag1() const { return _flag1; }
bool ProjectVersionData::flag2() const { return _flag2; }
bool ProjectVersionData::deleted() const { return _deleted; }
const char* ProjectVersionData::prcs_major() const { return _prcs_major; }
const char* ProjectVersionData::prcs_minor() const { return _prcs_minor; }
int ProjectVersionData::prcs_minor_int() const { return _prcs_minor_int; }
int ProjectVersionData::parent_count() const { return _parents.length(); }
int ProjectVersionData::parent_index(int p_num) const { return _parents.index(p_num); }
void ProjectVersionData::new_parent(int pi0) { _parents.append (pi0); }
int ProjectVersionData::version_index() const { return _index; }
void ProjectVersionData::deleted(bool del0) { _deleted = del0; }
void ProjectVersionData::prcs_major(const char* pm0) { _prcs_major = pm0; }
void ProjectVersionData::prcs_minor(const char* pm0) { _prcs_minor = pm0; _prcs_minor_int = atoi(pm0); }
bool ProjectVersionData::OK() const { return RcsVersionData::OK() && _prcs_major && _prcs_minor; }
ostream& operator<<(ostream& o, const ProjectVersionData* pvd)
{
o << pvd->_prcs_major << "." << pvd->_prcs_minor;
return o;
}
syntax highlighted by Code2HTML, v. 0.9.1