/* -*-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: rebuild.cc 1.3.1.1.1.8.1.21.1.4.1.4.1.14.1.6 Sun, 09 May 2004 18:21:12 -0700 jmacd $ */ extern "C" { #include "md5.h" } #include "prcs.h" #include "repository.h" #include "misc.h" #include "vc.h" #include "setkeys.h" #include "checkout.h" #include "hash.h" #include "system.h" #include "memseg.h" #include "rebuild.h" #include "prcsdir.h" #include "projdesc.h" #include "fileent.h" #include "syscmd.h" #include "checkin.h" #if __GNUG__ > 3 || (__GNUG__ == 3 && __GNUC_MINOR__ >= 2) #include #endif struct RebuildCallbackData { RepEntry* rep_entry; RcsFileTable* file_table; }; static const char *old_data_file_headers[] = { "\158\233\071\375", "\159\234\072\376", NULL }; static const char data_file_header[] = "\156\234\072\376"; static const int data_file_length = 4; static const char project_summary_header[] = "@"; static const char rcs_file_summary_header[] = "#"; static const char version_count_header[] = "$"; const char prcs_data_file_name[] = "prcs_data"; static MemorySegment read_buf(false); static PrRcsDeltaPtrError rebuild_repository_file(RepEntry* rep_entry, const char* name); /**********************************************************************/ /* Miscellaneous Debris */ /**********************************************************************/ /* Simply compute the md5 checksum of a memory segment */ char* md5_buffer(const char* buffer, int buflen) { MD5_CTX context; static unsigned char digest[16]; MD5Init(&context); MD5Update(&context, (unsigned char*)buffer, buflen); MD5Final(digest, &context); return (char*)digest; } /* Determines whether a file is garbage by looking at its extension and * for the "prcs_" prefix. */ static bool is_garbage_by_name(const char* pathname) { const char* name = strip_leading_path(pathname); int len = strlen(name); if(strcmp(name + len - 2, ",v") == 0) return false; else if(strncmp(name, "prcs_", 5) == 0) return false; else return true; } /* Determines whether a file is used internally by PRCS, by looking at the * prefix. */ static bool is_prcs_file(const char* pathname, const char* project_file_name) { const char* name = strip_leading_path(pathname); const char* projname = strip_leading_path(project_file_name); if(strncmp(name, "prcs_", 5) == 0) return true; else if(strcmp(name, projname) == 0) return true; else return false; } /* Writes the data file header, consisting of the string "This file contains ..." * and the project name */ static void write_data_file_header(ostream& os, const char* /*name*/) { os.write(data_file_header, data_file_length); } /* Compute the checksum of a file by checkin it out into memory, * and calling md5_buffer. */ static PrVoidError compute_cksum(const char* versionfile, RcsVersionData* ver) { FILE* cofile; Return_if_fail(cofile << VC_checkout_stream(ver->rcs_version(), versionfile)); // Blah Dstring ds; describe_versionfile (versionfile, ver, ds); Return_if_fail(read_buf.map_file(ds.cast(), fileno(cofile))); ver->length(read_buf.length()); Return_if_fail(VC_close_checkout_stream(cofile, ver->rcs_version(), versionfile)); ver->unkeyed_checksum(md5_buffer(read_buf.segment(), read_buf.length())); return NoError; } /* Create a data file with no versions in it, for initializing a repository */ PrVoidError create_empty_data(const char* project, const char* filename0) { ofstream new_data; new_data.open(filename0); write_data_file_header(new_data, project); if(new_data.bad()) { pthrow prcserror << "Failed creating new repository data file " << squote(filename0) << perror; } return NoError; } /**********************************************************************/ /* Write Methods */ /**********************************************************************/ /* Write out the ProjectVersionData structure */ static void write_data_file_project_info(ostream& os, ProjectVersionData* ver) { os << ver->date() << '\0'; os << ver->author() << '\0'; os << ver->rcs_version() << '\0'; os << (int)ver->deleted() << '\0'; os << ver->prcs_major() << '\0' << ver->prcs_minor() << '\0'; os << ver->parent_count() << '\0'; for (int i = 0; i < ver->parent_count(); i += 1) os << ver->parent_index(i) << '\0'; } /* Write out the RcsVersionData structure */ static void write_data_file_rcs_info(ostream& os, RcsVersionData* ver) { os << ver->date() << '\0'; os << ver->length() << '\0'; os << ver->author() << '\0'; os << ver->rcs_version() << '\0'; os << ver->plus_lines () << '\0'; os << ver->minus_lines () << '\0'; os.write(ver->unkeyed_checksum(), 16); } /* Write out a ProjectVersionData for each version in the project */ static void write_data_file_project_summary(ostream& os, ProjectVersionDataPtrArray *project_summary) { os << project_summary_header << project_summary->length() << '\0'; foreach(proj_ptr2, project_summary, ProjectVersionDataPtrArray::ArrayIterator) write_data_file_project_info(os, *proj_ptr2); } static void write_data_file_rcs_file_summary(ostream&os, const char* name, RcsDelta* delta) { if (delta->count() == 0) return; /* Here because reference_files isn't removing files it deletes * from the table and this is easier. */ os << rcs_file_summary_header << name << '\0'; os << version_count_header << delta->count() << '\0'; foreach(ver, delta, RcsDelta::DeltaIterator) write_data_file_rcs_info(os, *ver); } /**********************************************************************/ /* Deletion Methods */ /**********************************************************************/ /* Delete a single version from a single file. */ /* Whether RCS really deletes the version or not is ignored */ static PrVoidError delete_version(const char* file_name, const char* version) { if(!option_report_actions) { bool deleted; Return_if_fail(deleted << VC_delete_version(version, file_name)); if(!deleted) return NoError; } if(option_long_format) prcsoutput << (option_report_actions ? "Delete" : "Deleted") << " unreferenced version " << version << " from version file " << squote(file_name) << dotendl; return NoError; } /* Delete all unreferenced versions from an RCS file */ static PrVoidError delete_unreferenced_versions_file(RepEntry* rep_entry, const char* file_name, RcsDelta* delta) { foreach(data_ptr, delta, RcsDelta::DeltaIterator) { RcsVersionData* data = *data_ptr; if(data->referenced() == RcsVersionData::Referenced) continue; rep_entry->Rep_log() << "Deleteing version " << data->rcs_version() << " from file " << file_name << dotendl; delta->remove(data->rcs_version()); Return_if_fail(delete_version(file_name, data->rcs_version())); } return NoError; } static PrVoidError ignoring(ProjectVersionData* project_data) { prcswarning << "Project version " << project_data << "'s project file is invalid " "and will be ignored" << dotendl; bug (); return NoError; } /* Mark all versions referenced by a single version of a project * in the file table. */ static PrVoidError reference_versions(const char* project_file_name, ProjectVersionData* project_data, RcsFileTable* file_table) { FILE* cofile; ProjectDescriptor* project; Dstring name; Return_if_fail(cofile << VC_checkout_stream(project_data->rcs_version(), project_file_name)); name.sprintf("%s:%s", project_file_name, project_data->rcs_version()); If_fail(project << read_project_file(cmd_root_project_name, strip_leading_path(name), false, cofile, KeepNothing)) return ignoring(project_data); Return_if_fail(VC_close_checkout_stream(cofile, project_data->rcs_version(), project_file_name)); foreach_fileent (fe_ptr, project) { FileEntry *fe = *fe_ptr; if (fe->file_type() == RealFile && !fe->descriptor_name()) return ignoring(project_data); } foreach_fileent(fe_ptr, project) { RcsDelta **lookup, *delta; RcsVersionData* version_data; FileEntry *fe = *fe_ptr; if(fe->file_type() != RealFile) continue; if((lookup = file_table->lookup(fe->descriptor_name())) == NULL) { /*delete project;*/ pthrow prcserror << "Version file " << squote(fe->descriptor_name()) << " referenced by project version " << project_data << " but not found, cannot clean repository" << dotendl; } delta = *lookup; version_data = delta->lookup(fe->descriptor_version_number()); if(!version_data) { /*delete project;*/ pthrow prcserror << "Version " << fe->descriptor_version_number() << " in repository file " << squote(fe->descriptor_name()) << " referenced by project version " << project_data << " but not found, cannot clean repository" << dotendl; } version_data->reference(RcsVersionData::Referenced); } /* @@@ This is where the more-complete parent-version consistency check should go. */ delete project; return NoError; } /* For each file, call delete_unreferenced_versions_file */ static PrVoidError delete_unreferenced_versions(RepEntry* rep_entry) { ProjectVersionDataPtrArray* project_summary = rep_entry->project_summary(); RcsFileTable* file_table = rep_entry->rcs_file_summary(); foreach(project_data_ptr, project_summary, ProjectVersionDataPtrArray::ArrayIterator) { if((*project_data_ptr)->deleted()) continue; Return_if_fail(reference_versions(rep_entry->Rep_name_of_version_file(), *project_data_ptr, file_table)); } foreach(file_pair_ptr, file_table, RcsFileTable::HashIterator) { Dstring name(rep_entry->Rep_name_in_entry((*file_pair_ptr).x())); name.append (",v"); Return_if_fail(delete_unreferenced_versions_file(rep_entry, name, (*file_pair_ptr).y())); if((*file_pair_ptr).y()->count() == 0) { /* Takes care of deleteing the empty file */ rebuild_repository_file(rep_entry, name); } } return NoError; } /**********************************************************************/ /* Gather Methods */ /**********************************************************************/ /* Read one RCS file's rlog, and return an RcsDelta table */ static PrRcsDeltaPtrError rebuild_repository_file(RepEntry* rep_entry, const char* name) { RcsDelta* data; Return_if_fail(data << VC_get_version_data(name)); if(data->count() == 0) { if(option_long_format) { prcsoutput << (option_report_actions ? "Delete" : "Deleting") << " empty version file " << squote(strip_leading_path(name)) << " from repository" << dotendl; } rep_entry->Rep_log() << "Deleting empty file " << squote(name) << dotendl; if(!option_report_actions) { If_fail(Err_unlink(name)) pthrow prcserror << "Unlink failed on " << squote(name) << perror; } return (RcsDelta*)0; } /* @@@ Ideally, the cksum would get stored in the RCS log message so that this step * could actually verify, not simply re-compute the cksum. */ foreach(rev_ptr, data, RcsDelta::DeltaIterator) { Return_if_fail(compute_cksum(name, *rev_ptr)); } return data; } static PrVoidError rebuild_file_table_file (const char* name, const void* data) { RepEntry* rep_entry = ((RebuildCallbackData*)data)->rep_entry; RcsFileTable* file_table = ((RebuildCallbackData*)data)->file_table; if(fs_is_symlink(name) || is_garbage_by_name(name)) { rep_entry->Rep_log() << "Deleting garbage " << squote(name) << dotendl; /* Delete it! */ if(!option_report_actions) { If_fail(Err_unlink(name)) { prcswarning << "Failed removing garbage file " << squote(name) << " from repository" << dotendl; return NoError; } } if(option_long_format) { prcsoutput << (option_report_actions ? "Delete" : "Deleting") << " garbage file " << squote(name) << " from repository" << dotendl; } } else if(!is_prcs_file(name, rep_entry->Rep_name_of_version_file())) { /* Found an RCS file, run rebuild */ RcsDelta* delta_ptr; Return_if_fail(delta_ptr << rebuild_repository_file(rep_entry, name)); if(!delta_ptr) return NoError; Dstring saved_name (name + strlen(rep_entry->Rep_entry_path()) + 1); saved_name.truncate (saved_name.length() - 2); file_table->insert(p_strdup(saved_name.cast()), delta_ptr); } return NoError; } static PrRcsFileTablePtrError rebuild_file_table(RepEntry* rep_ptr) { RcsFileTable* file_table = new RcsFileTable; RebuildCallbackData data; data.rep_entry = rep_ptr; data.file_table = file_table; Return_if_fail(directory_recurse (rep_ptr->Rep_entry_path(), &data, rebuild_file_table_file)); return file_table; } /* This mainly checks for duplicate version numbers, a symptom of the * bug reported by Keith Owens in versions up to 1.3.0. Also checks * for invalid parent-version indices. */ static PrVoidError rebuild_check_project_summary (ProjectVersionDataPtrArray* project_summary) { ProjectVersionDataPtrArray copy (*project_summary); /* Loop through a copy of the project summary, once for each major * version, checking for duplicate minor version numbers. (Note: * similarly ugly code to info_array() -- need to simplify this * kind of iteration) */ for (int i = 0; i < copy.length (); i += 1) { if (copy.index (i) != NULL) { const char *major = copy.index(i)->prcs_major (); int minor = 0; for (int j = i; j < copy.length (); j += 1) { ProjectVersionData *check = copy.index(j); if (check == NULL) { continue; } if (strcmp (major, check->prcs_major ()) == 0) { int this_minor = check->prcs_minor_int (); if (this_minor <= minor) { pthrow prcserror << ((this_minor <= 0) ? "Invalid" : "Duplicate") << " project version " << check << " found, please contact " << maintainer << " for assistance with this problem" << dotendl; } while (++minor < this_minor) { /* I made this a warning not an error simply because there is a * missing version in PRCS's own repository (from Feb 20, 1996, * and I don't remember why). If this is a genuine error it needs * to be caught somewhere else. A check should be inserted in * reference_versions when the project file is actually * checked-out that the parent-indices match reality. */ prcswarning << "Missing any record of project version " << major << "." << minor << prcsendl; } ASSERT (check->version_index () == j, "set by VC_get_project_version_data"); for (int k = 0; k < check->parent_count (); k += 1) { int p_i = check->parent_index (k); if (p_i < 0 || p_i >= j) { pthrow prcserror << "Invalid parent-version index found for project version " << check << ", please contact " << maintainer << " for assitance with this problem" << dotendl; } } copy.index (j, (ProjectVersionData*)0); } } } } return NoError; } /* This is the top-level call to rebuild a repository data file. */ static PrVoidError rebuild_repository(RepEntry *rep_ptr, ProjectVersionDataPtrArray* project_summary, RcsFileTable* file_table) { if(!project_summary) { Return_if_fail(project_summary << VC_get_project_version_data(rep_ptr->Rep_name_of_version_file())); } Return_if_fail(rebuild_check_project_summary (project_summary)); if(!file_table) { Return_if_fail(file_table << rebuild_file_table(rep_ptr)); } /* Unlink here in case we're mmaping the file that's about to get truncated. */ If_fail(Err_unlink(rep_ptr->Rep_name_in_entry(prcs_data_file_name))) { pthrow prcserror << "Unlink failed on old repository data file " << squote(rep_ptr->Rep_name_in_entry(prcs_data_file_name)) << perror; } rep_ptr->project_summary(project_summary); rep_ptr->rcs_file_summary(file_table); If_fail(delete_unreferenced_versions(rep_ptr)) prcsinfo << "Continuing to build repository data file, " "no cleaning was performed" << dotendl; if (option_report_actions) { prcsinfo << "Rebuild will be likely successful, the repository appears to be well" << dotendl; return NoError; } ofstream new_data(rep_ptr->Rep_name_in_entry(prcs_data_file_name)); write_data_file_header(new_data, cmd_root_project_name); write_data_file_project_summary(new_data, project_summary); foreach(file_pair_ptr, file_table, RcsFileTable::HashIterator) { write_data_file_rcs_file_summary(new_data, (*file_pair_ptr).x(), (*file_pair_ptr).y()); } new_data.close(); if(new_data.bad()) { pthrow prcserror << "Write failed to reconstructed project data file " << squote(rep_ptr->Rep_name_in_entry(prcs_data_file_name)) << perror; } return NoError; } /* Run rebuild with an opened repository */ PrVoidError admin_rebuild_command_no_open(RepEntry* rep, bool valid_rep_data) { Umask mask (rep->Rep_get_umask()); Return_if_fail(rep->Rep_uncompress_all_files()); if(valid_rep_data) Return_if_fail(rebuild_repository(rep, rep->project_summary(), rep->rcs_file_summary())); else Return_if_fail(rebuild_repository(rep, NULL, NULL)); Return_if_fail(rep->Rep_compress_all_files()); Return_if_fail(rep->Rep_make_default_tag ()); return NoError; } /* This is it. */ PrPrcsExitStatusError admin_rebuild_command() { RepEntry *rep; Return_if_fail(rep << Rep_init_repository_entry(cmd_root_project_name, true, false, false)); rep->Rep_log() << "Rebuilding project" << dotendl; prcsinfo << "This command may run for a long time" << dotendl; Return_if_fail(admin_rebuild_command_no_open(rep, false)); rep->Rep_log() << "Suceeded rebuilding project" << dotendl; return ExitSuccess; } /* Mark a project version as deleted by changing its log information */ static PrVoidError mark_deleted(ProjectVersionData* project_data, RepEntry* rep_entry) { Dstring log; project_data->deleted(true); format_version_log(project_data, log); Return_if_fail(VC_set_log(log.cast(), project_data->rcs_version(), rep_entry->Rep_name_of_version_file())); return NoError; } /* This is it, too. */ PrPrcsExitStatusError delete_command() { RepEntry *rep_entry; ProjectVersionData* project_data; Return_if_fail(rep_entry << Rep_init_repository_entry(cmd_root_project_name, true, false, true)); Return_if_fail(project_data << resolve_version(cmd_version_specifier_major, cmd_version_specifier_minor, cmd_root_project_full_name, cmd_root_project_file_path, NULL, rep_entry)); #if 0 /*@@@ I have no idea about this*/ if(project_data->prcs_minor_int() == 0) pthrow prcserror << "You may not delete minor version 0" << dotendl; #endif if(project_data->deleted()) pthrow prcserror << "That version is already deleted" << dotendl; prcsquery << "Delete version " << project_data << report("") << defopt('y', "You know what you're doing, go ahead") << optfail('n') << query(". Are you sure"); Return_if_fail(prcsquery.result()); if(option_report_actions) return ExitSuccess; rep_entry->Rep_log() << "Deleting version " << project_data << dotendl; Return_if_fail(mark_deleted(project_data, rep_entry)); Return_if_fail(admin_rebuild_command_no_open(rep_entry, true)); rep_entry->Rep_log() << "Suceeded deleting version" << dotendl; return ExitSuccess; } /* READ */ /* Called when something goes wrong reading a data file */ PrVoidError RebuildFile::bad_data_file() { pthrow prcserror << "Invalid repository data file " << squote(source_name()) << ". Please run " << squote("prcs admin rebuild") << " to rebuild the data file" << dotendl; } /* Read the header and assert that the projects match */ PrVoidError RebuildFile::read_header() { const char* s; s = get_string(data_file_length); if (!s) return bad_data_file(); for (int i = 0; old_data_file_headers[i]; i += 1) { if (strncmp (s, old_data_file_headers[i], data_file_length) == 0) { pthrow prcserror << "The repository data file is outdated, you must run " << squote ("prcs admin rebuild") << " to rebuild the data file" << dotendl; } } if (strncmp (s, data_file_header, data_file_length) != 0) return bad_data_file(); return NoError; } /* Read a single project version */ PrProjectVersionDataPtrError RebuildFile::read_project_version(int i) { ProjectVersionData* ver = new ProjectVersionData(i); int get; if((get = get_size()) < 0) return bad_data_file(); ver->date(get); ver->author(get_string()); ver->rcs_version(get_string()); if(!(ver->author() && ver->rcs_version())) return bad_data_file(); if((get = get_size()) < 0) return bad_data_file(); ver->deleted(get); ver->prcs_major(get_string()); ver->prcs_minor(get_string()); if ((get = get_size()) < 0) return bad_data_file(); for (int i = 0, c = get; i < c; i += 1) { if ((get = get_size()) < 0) return bad_data_file(); ver->new_parent(get); } return ver; } /* Read a single RCS version */ PrRcsVersionDataPtrError RebuildFile::read_rcs_version() { RcsVersionData* ver = new RcsVersionData; const char* cksum; const char *pls, *mls; ver->date(get_size()); ver->length(get_size()); if(ver->date() < 0 || ver->length() < 0) return bad_data_file(); ver->author(get_string()); ver->rcs_version(get_string()); if(!(ver->author() && ver->rcs_version())) return bad_data_file(); if (! (pls = get_string ()) || ! (mls = get_string ())) return bad_data_file (); ver->set_plus_lines (pls); ver->set_minus_lines (mls); if(!(cksum = get_string(16))) return bad_data_file(); ver->unkeyed_checksum(cksum); return ver; } /* Read some number of project versions */ PrVoidError RebuildFile::read_project_summary(int& count) { int version_count; ProjectVersionData* ver; if((version_count = get_size()) < 0) return bad_data_file(); for(int i = 0; i < version_count; i += 1) { Return_if_fail(ver << read_project_version(count++)); project_data->append(ver); } return NoError; } /* Read some number of RCS file summaries */ PrVoidError RebuildFile::read_rcs_file_summary() { const char *read_file_name; RcsDelta **lookup, *delta_array; int num; if((read_file_name = get_string()) == NULL) return bad_data_file(); if((lookup = rcs_file_table->lookup(read_file_name)) == NULL) { delta_array = new RcsDelta(NULL); rcs_file_table->insert(read_file_name, delta_array); } else { delta_array = *lookup; } if(!get_string(version_count_header)) return bad_data_file(); if((num = get_size()) < 0) return bad_data_file(); for(int i = 0; i < num; i += 1) { RcsVersionData *ver; Return_if_fail(ver << read_rcs_version()); delta_array->insert(ver->rcs_version(), ver); ASSERT(delta_array->lookup(ver->rcs_version()) == ver, "hope so"); } return NoError; } /* the following RebuildFile methods deal with reading or expecting strings * and positive ints from a file. */ PrRebuildFilePtrError read_repository_data(const char* filename0, bool write) { RebuildFile* file = new RebuildFile; Return_if_fail(file->init_from_file(filename0, write)); return file; } PrVoidError RebuildFile::init_from_file(const char* filename0, bool write) { filename.assign(filename0); seg = new MemorySegment(false, write); Return_if_fail(seg->map_file(filename0)); last = seg->segment() + seg->length(); offset = 0; Return_if_fail(read_header()); rcs_file_table = new RcsFileTable; project_data = new ProjectVersionDataPtrArray; const char* s; int version_count = 0; while((s = get_string(strlen(project_summary_header))) != NULL) { if(strncmp(s, project_summary_header, strlen(project_summary_header)) == 0) Return_if_fail(read_project_summary(version_count)); else if(strncmp(s, rcs_file_summary_header, strlen(rcs_file_summary_header)) == 0) Return_if_fail(read_rcs_file_summary()); else return bad_data_file(); } if(done()) return NoError; else return bad_data_file(); } const char* RebuildFile::get_string() { const char* val = seg->segment() + offset; if(seg->length() < offset) return NULL; offset += strlen(val) + 1; return val; } const char* RebuildFile::get_string(int len) { if(last - (seg->segment() + offset) < len || seg->length() < offset) return NULL; const char* val = seg->segment() + offset; offset += len; return val; } bool RebuildFile::get_string(const char* expected, bool term) { int len = strlen(expected); if(last - (seg->segment() + offset) < len || seg->length() < offset) return false; if(strncmp(expected, seg->segment() + offset, len) != 0) return false; offset += len; if(term) offset += 1; return true; } int RebuildFile::get_size() { const char* p = seg->segment() + offset; char c = 0; int val = 0; while(p < last && (c = *p++) != 0) { if(c < '0' || c > '9') return -1; val *= 10; val += (c - '0'); } offset = p - seg->segment(); if(p <= last && c == 0) return val; return -1; } const char* RebuildFile::source_name() { return filename; } bool RebuildFile::done() { return offset == seg->length(); } void RebuildFile::init_stream() { if(!buf) { #if __GNUG__ < 3 buf = new filebuf(seg->fd()); buf->seekoff(0,ios::end); #elif __GNUG__ == 3 and __GNUC_MINOR__ < 2 buf = new filebuf(fdopen(dup(seg->fd()), "a+"), ios::out); buf->pubseekoff(0, ios::end, ios::out); #else buf = new __gnu_cxx::stdio_filebuf (seg->fd(), ios::out, false /* close */, default_segment_size); buf->pubseekoff(0, ios::end, ios::out); #endif os = new ostream(buf); } } /* The following three methods append new data to the file */ void RebuildFile::add_project_data(ProjectVersionData* data) { init_stream(); (*os) << project_summary_header << 1 << '\0'; write_data_file_project_info(*os, data); } void RebuildFile::add_rcs_file_data(const char* new_file_name, RcsVersionData* data) { init_stream(); (*os) << rcs_file_summary_header << new_file_name << '\0'; (*os) << version_count_header << 1 << '\0'; write_data_file_rcs_info(*os, data); } PrVoidError RebuildFile::update_project_data() { os->flush(); if(os->fail()) goto error; delete os; delete buf; os = NULL; buf = NULL; If_fail(seg->unmap()) goto error; delete seg; seg = NULL; return NoError; error: pthrow prcserror << "Failed updating repository data file" << squote(source_name()) << perror; } RebuildFile::~RebuildFile() { delete seg; foreach(data_ptr, project_data, ProjectVersionDataPtrArray::ArrayIterator) delete (*data_ptr); delete project_data; foreach(file_ptr, rcs_file_table, RcsFileTable::HashIterator) { delete (*file_ptr).y(); } delete rcs_file_table; } ProjectVersionDataPtrArray* RebuildFile::get_project_summary() const { return project_data; } RcsFileTable* RebuildFile::get_rcs_file_summary() const { return rcs_file_table; } RebuildFile::RebuildFile() :buf(NULL), os(NULL) { }