*** empty log message ***

This commit is contained in:
David Rose 2000-11-01 18:36:39 +00:00
parent a85771f39d
commit 527b16a099
11 changed files with 1473 additions and 8 deletions

View File

@ -0,0 +1,28 @@
#begin lib_target
#define TARGET cvscopy
#define LOCAL_LIBS \
progbase pandatoolbase
#define OTHER_LIBS \
dconfig:c dtool:m
#define SOURCES \
cvsCopy.cxx cvsCopy.h cvsSourceDirectory.cxx cvsSourceDirectory.h \
cvsSourceTree.cxx cvsSourceTree.h
#define INSTALL_HEADERS \
cvsCopy.h
#end lib_target
#begin test_bin_target
#define TARGET testcopy
#define LOCAL_LIBS cvscopy
#define OTHER_LIBS \
dconfig:c dtool:m pystub
#define SOURCES \
testCopy.cxx testCopy.h
#end test_bin_target

View File

@ -0,0 +1,278 @@
// Filename: cvsCopy.cxx
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
#include "cvsCopy.h"
#include "cvsSourceDirectory.h"
#include <notify.h>
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CVSCopy::
CVSCopy() {
_model_dirname = ".";
_key_filename = "Sources.pp";
_cvs_binary = "cvs";
_model_dir = (CVSSourceDirectory *)NULL;
_map_dir = (CVSSourceDirectory *)NULL;
clear_runlines();
add_runline("[opts] file [file ... ]");
add_option
("f", "", 80,
"Force copy to happen without any input from the user. If a file "
"with the same name exists anywhere in the source hierarchy, it will "
"be overwritten without prompting; if a file does not yet exist, it "
"will be created in the directory named by -d or by -m, as appropriate.",
&CVSCopy::dispatch_none, &_force);
add_option
("i", "", 80,
"The opposite of -f, this will prompt the user before each action. "
"The default is only to prompt the user when an action is ambiguous.",
&CVSCopy::dispatch_none, &_interactive);
add_option
("d", "dirname", 80,
"Copy model files that are not already present somewhere in the tree "
"to the indicated directory. The default is the current directory.",
&CVSCopy::dispatch_filename, &_got_model_dirname, &_model_dirname);
add_option
("m", "dirname", 80,
"Copy texture map files to the indicated directory. The default "
"is src/maps from the root directory.",
&CVSCopy::dispatch_filename, &_got_map_dirname, &_map_dirname);
add_option
("root", "dirname", 80,
"Specify the root of the CVS source hierarchy. The default is to "
"use the ppremake convention of locating the directory containing "
"Package.pp.",
&CVSCopy::dispatch_filename, &_got_root_dirname, &_root_dirname);
add_option
("key", "filename", 80,
"Specify the name of the file that must exist in each directory for "
"it to be considered part of the CVS source hierarchy. The default "
"is the ppremake convention, \"Sources.pp\". Other likely candidates "
"are \"CVS\" to search the entire CVS hierarchy, or \".\" to include "
"all subdirectories.",
&CVSCopy::dispatch_filename, NULL, &_key_filename);
add_option
("nc", "", 80,
"Do not attempt to add newly-created files to CVS. The default "
"is to add them.",
&CVSCopy::dispatch_none, &_no_cvs);
add_option
("cvs", "cvs_binary", 80,
"Specify how to run the cvs program for adding newly-created files. "
"The default is simply \"cvs\".",
&CVSCopy::dispatch_string, NULL, &_cvs_binary);
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::import
// Access: Public
// Description: Checks for the given filename somewhere in the
// directory hierarchy, and chooses a place to import
// it. Copies the file by calling copy_file().
//
// Type is an integer number that is defined by the
// derivated class; CVSCopy simply passes it unchanged
// to copy_file(). It presumably gives the class a hint
// as to how the file should be copied. Suggested_dir
// is the suggested directory in which to copy the file,
// if it does not already exist elsewhere.
//
// On success, returns the CVSSourceDirectory it was
// actually copied to. On failure, returns NULL.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSCopy::
import(const Filename &source, int type, CVSSourceDirectory *suggested_dir) {
CopiedFiles::const_iterator ci;
ci = _copied_files.find(source);
if (ci != _copied_files.end()) {
// We have already copied this file.
return (*ci).second;
}
if (!source.exists()) {
cerr << "Source filename " << source << " does not exist!\n";
return (CVSSourceDirectory *)NULL;
}
string basename = source.get_basename();
CVSSourceDirectory *dir =
_tree.choose_directory(basename, suggested_dir, _force, _interactive);
nassertr(dir != (CVSSourceDirectory *)NULL, dir);
Filename dest = dir->get_fullpath() + "/" + basename;
_copied_files[source] = dir;
bool new_file = !dest.exists();
if (!copy_file(source, dest, dir, type, new_file)) {
return (CVSSourceDirectory *)NULL;
}
if (new_file) {
create_file(dest);
}
return dir;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::handle_args
// Access: Protected, Virtual
// Description: Does something with the additional arguments on the
// command line (after all the -options have been
// parsed). Returns true if the arguments are good,
// false otherwise.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
handle_args(Args &args) {
if (args.empty()) {
nout << "You must specify the file(s) to copy from on the command line.\n";
return false;
}
_source_files = args;
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::post_command_line
// Access: Protected, Virtual
// Description: This is called after the command line has been
// completely processed, and it gives the program a
// chance to do some last-minute processing and
// validation of the options and arguments. It should
// return true if everything is fine, false if there is
// an error.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
post_command_line() {
if (!scan_hierarchy()) {
return false;
}
_model_dir = _tree.find_directory(_model_dirname);
if (_model_dir == (CVSSourceDirectory *)NULL) {
if (_got_model_dirname) {
nout << "Warning: model directory " << _model_dirname
<< " is not within the source hierarchy.\n";
}
}
if (_got_map_dirname) {
_map_dir = _tree.find_directory(_map_dirname);
if (_map_dir == (CVSSourceDirectory *)NULL) {
nout << "Warning: map directory " << _map_dirname
<< " is not within the source hierarchy.\n";
}
} else {
_map_dir = _tree.find_relpath("src/maps");
if (_map_dir == (CVSSourceDirectory *)NULL) {
nout << "Warning: no directory " << _tree.get_root_dirname()
<< "/src/maps.\n";
_map_dir = _model_dir;
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::create_file
// Access: Protected
// Description: Invokes CVS to add the indicated filename to the
// repository, if the user so requested. Returns true
// if successful, false if there is an error.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
create_file(const Filename &filename) {
if (_no_cvs) {
return true;
}
if (!CVSSourceTree::temp_chdir(filename.get_dirname())) {
nout << "Invalid directory: " << filename.get_dirname() << "\n";
return false;
}
string command = _cvs_binary + " add " + filename.get_basename();
nout << command << "\n";
int result = system(command.c_str());
CVSSourceTree::restore_cwd();
if (result != 0) {
nout << "Failure invoking cvs.\n";
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::scan_hierarchy
// Access: Private
// Description: Starts the scan of the source hierarchy. This
// identifies all of the files in the source hierarchy
// we're to copy these into, so we can guess where
// referenced files should be placed. Returns true if
// everything is ok, false if there is an error.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
scan_hierarchy() {
if (!_got_root_dirname) {
// If we didn't get a root directory name, find the directory
// above this one that contains the file "Package.pp".
if (!scan_for_root(_model_dirname)) {
return false;
}
}
_tree.set_root(_root_dirname);
nout << "Root is " << _tree.get_root_fullpath() << "\n";
return _tree.scan(_key_filename);
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::scan_for_root
// Access: Private
// Description: Searches for the root of the source directory by
// looking for the parent directory that contains
// "Package.pp". Returns true on success, false on
// failure.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
scan_for_root(const string &dirname) {
Filename sources = dirname + "/Sources.pp";
if (!sources.exists()) {
nout << "Couldn't find " << sources << " in source directory.\n";
return false;
}
Filename package = dirname + "/Package.pp";
if (package.exists()) {
// Here's the root!
_root_dirname = dirname;
return true;
}
return scan_for_root(dirname + "/..");
}

View File

@ -0,0 +1,67 @@
// Filename: cvsCopy.h
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
#ifndef CVSCOPY_H
#define CVSCOPY_H
#include <pandatoolbase.h>
#include "cvsSourceTree.h"
#include <programBase.h>
#include <filename.h>
////////////////////////////////////////////////////////////////////
// Class : CVSCopy
// Description : This is the base class for a family of programs that
// copy files, typically model files like .flt files and
// their associated textures, into a CVS-controlled
// source tree.
////////////////////////////////////////////////////////////////////
class CVSCopy : public ProgramBase {
public:
CVSCopy();
CVSSourceDirectory *
import(const Filename &source, int type, CVSSourceDirectory *suggested_dir);
protected:
virtual bool handle_args(Args &args);
virtual bool post_command_line();
virtual bool copy_file(Filename source, Filename dest,
CVSSourceDirectory *dest_dir,
int type, bool new_file)=0;
bool create_file(const Filename &filename);
private:
bool scan_hierarchy();
bool scan_for_root(const string &dirname);
protected:
bool _force;
bool _interactive;
bool _got_model_dirname;
Filename _model_dirname;
bool _got_map_dirname;
Filename _map_dirname;
bool _got_root_dirname;
Filename _root_dirname;
Filename _key_filename;
bool _no_cvs;
string _cvs_binary;
typedef vector_string SourceFiles;
SourceFiles _source_files;
CVSSourceTree _tree;
CVSSourceDirectory *_model_dir;
CVSSourceDirectory *_map_dir;
typedef map<string, CVSSourceDirectory *> CopiedFiles;
CopiedFiles _copied_files;
};
#endif

View File

@ -0,0 +1,270 @@
// Filename: cvsSourceDirectory.cxx
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
#include "cvsSourceDirectory.h"
#include "cvsSourceTree.h"
#include <notify.h>
#ifdef WIN32_VC
// Windows uses a different API for scanning for files in a directory.
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#else
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#endif
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CVSSourceDirectory::
CVSSourceDirectory(CVSSourceTree *tree, CVSSourceDirectory *parent,
const string &dirname) :
_tree(tree),
_parent(parent),
_dirname(dirname)
{
if (_parent == (CVSSourceDirectory *)NULL) {
_depth = 0;
} else {
_depth = _parent->_depth + 1;
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CVSSourceDirectory::
~CVSSourceDirectory() {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
delete (*ci);
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::get_dirname
// Access: Public
// Description: Returns the local name of this particular directory.
////////////////////////////////////////////////////////////////////
string CVSSourceDirectory::
get_dirname() const {
return _dirname;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::get_fullpath
// Access: Public
// Description: Returns the full pathname to this particular
// directory.
////////////////////////////////////////////////////////////////////
string CVSSourceDirectory::
get_fullpath() const {
if (_parent == (CVSSourceDirectory *)NULL) {
return _tree->get_root_fullpath();
}
return _parent->get_fullpath() + "/" + _dirname;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::get_path
// Access: Public
// Description: Returns the relative pathname to this particular
// directory, as seen from the root of the tree.
////////////////////////////////////////////////////////////////////
string CVSSourceDirectory::
get_path() const {
if (_parent == (CVSSourceDirectory *)NULL) {
return _dirname;
}
return _parent->get_path() + "/" + _dirname;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::get_rel_to
// Access: Public
// Description: Returns the relative path to the other directory from
// this one. This does not include a trailing slash.
////////////////////////////////////////////////////////////////////
string CVSSourceDirectory::
get_rel_to(const CVSSourceDirectory *other) const {
const CVSSourceDirectory *a = this;
const CVSSourceDirectory *b = other;
if (a == b) {
return ".";
}
string prefix, postfix;
while (a->_depth > b->_depth) {
prefix += "../";
a = a->_parent;
assert(a != (CVSSourceDirectory *)NULL);
}
while (b->_depth > a->_depth) {
postfix = b->_dirname + "/" + postfix;
b = b->_parent;
assert(b != (CVSSourceDirectory *)NULL);
}
while (a != b) {
prefix += "../";
postfix = b->_dirname + "/" + postfix;
a = a->_parent;
b = b->_parent;
assert(a != (CVSSourceDirectory *)NULL);
assert(b != (CVSSourceDirectory *)NULL);
}
string result = prefix + postfix;
assert(!result.empty());
return result.substr(0, result.length() - 1);
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::get_num_children
// Access: Public
// Description: Returns the number of subdirectories below this
// directory.
////////////////////////////////////////////////////////////////////
int CVSSourceDirectory::
get_num_children() const {
return _children.size();
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::get_child
// Access: Public
// Description: Returns the nth subdirectory below this directory.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceDirectory::
get_child(int n) const {
nassertr(n >= 0 && n < (int)_children.size(), NULL);
return _children[n];
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::find_relpath
// Access: Public
// Description: Returns the source directory that corresponds to the
// given relative path from this directory, or NULL if
// there is no match.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceDirectory::
find_relpath(const string &relpath) {
if (relpath.empty()) {
return this;
}
size_t slash = relpath.find('/');
string first = relpath.substr(0, slash);
string rest;
if (slash != string::npos) {
rest = relpath.substr(slash + 1);
}
if (first.empty() || first == ".") {
return find_relpath(rest);
} else if (first == "..") {
if (_parent != NULL) {
return _parent->find_relpath(rest);
}
// Tried to back out past the root directory.
return (CVSSourceDirectory *)NULL;
}
// Check for a child named "first".
Children::const_iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
if ((*ci)->get_dirname() == first) {
return (*ci)->find_relpath(rest);
}
}
// No match.
return (CVSSourceDirectory *)NULL;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::find_dirname
// Access: Public
// Description: Returns the source directory that corresponds to the
// given local directory name, or NULL if there is no
// match.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceDirectory::
find_dirname(const string &dirname) {
if (dirname == _dirname) {
return this;
}
Children::const_iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
CVSSourceDirectory *result = (*ci)->find_dirname(dirname);
if (result != (CVSSourceDirectory *)NULL) {
return result;
}
}
return (CVSSourceDirectory *)NULL;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceDirectory::scan
// Access: Public
// Description: Recursively scans the contents of the source
// directory. Fullpath is the full path name to the
// directory; key_filename is the name of a file that
// must exist in each subdirectory for it to be
// considered part of the hierarchy. Returns true on
// success, false on failure.
////////////////////////////////////////////////////////////////////
bool CVSSourceDirectory::
scan(const string &fullpath, const string &key_filename) {
DIR *root = opendir(fullpath.c_str());
if (root == (DIR *)NULL) {
cerr << "Unable to scan directory " << fullpath << "\n";
return false;
}
struct dirent *d;
d = readdir(root);
while (d != (struct dirent *)NULL) {
string filename = d->d_name;
if (!filename.empty() && filename[0] != '.') {
// Is this possibly a subdirectory name?
string next_path = fullpath + "/" + filename;
string key = next_path + "/" + key_filename;
if (access(key.c_str(), F_OK) == 0) {
CVSSourceDirectory *subdir =
new CVSSourceDirectory(_tree, this, filename);
_children.push_back(subdir);
if (!subdir->scan(next_path, key_filename)) {
closedir(root);
return false;
}
} else {
// It's not a subdirectory; call it a regular file.
_tree->add_file(filename, this);
}
}
d = readdir(root);
}
closedir(root);
return true;
}

View File

@ -0,0 +1,52 @@
// Filename: cvsSourceDirectory.h
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
#ifndef CVSSOURCEDIRECTORY_H
#define CVSSOURCEDIRECTORY_H
#include <pandatoolbase.h>
#include <vector>
class CVSSourceTree;
////////////////////////////////////////////////////////////////////
// Class : CVSSourceDirectory
// Description : This represents one particular directory in the
// hierarchy of source directory files. We must scan
// the source directory to identify where the related
// files have previously been copied.
////////////////////////////////////////////////////////////////////
class CVSSourceDirectory {
public:
CVSSourceDirectory(CVSSourceTree *tree, CVSSourceDirectory *parent,
const string &dirname);
~CVSSourceDirectory();
string get_dirname() const;
string get_fullpath() const;
string get_path() const;
string get_rel_to(const CVSSourceDirectory *other) const;
int get_num_children() const;
CVSSourceDirectory *get_child(int n) const;
CVSSourceDirectory *find_relpath(const string &relpath);
CVSSourceDirectory *find_dirname(const string &dirname);
public:
bool scan(const string &prefix, const string &key_filename);
private:
CVSSourceTree *_tree;
CVSSourceDirectory *_parent;
string _dirname;
int _depth;
typedef vector<CVSSourceDirectory *> Children;
Children _children;
};
#endif

View File

@ -0,0 +1,543 @@
// Filename: cvsSourceTree.cxx
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
#include "cvsSourceTree.h"
#include "cvsSourceDirectory.h"
#include <filename.h>
#include <notify.h>
#include <algorithm>
#include <ctype.h>
#include <stdio.h> // for perror
#include <errno.h>
bool CVSSourceTree::_got_start_fullpath = false;
string CVSSourceTree::_start_fullpath;
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CVSSourceTree::
CVSSourceTree() {
_root = (CVSSourceDirectory *)NULL;
_got_root_fullpath = false;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CVSSourceTree::
~CVSSourceTree() {
if (_root != (CVSSourceDirectory *)NULL) {
delete _root;
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::set_root
// Access: Public
// Description: Sets the root of the source directory. This must be
// called before scan(), and should not be called more
// than once.
////////////////////////////////////////////////////////////////////
void CVSSourceTree::
set_root(const string &root_path) {
nassertv(_path.empty());
_path = root_path;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::scan
// Access: Public
// Description: Scans the complete source directory starting at the
// indicated pathname. It is an error to call this more
// than once. Returns true on success, false if there
// is an error.
////////////////////////////////////////////////////////////////////
bool CVSSourceTree::
scan(const string &key_filename) {
nassertr(_root == (CVSSourceDirectory *)NULL, false);
Filename root_fullpath = get_root_fullpath();
_root = new CVSSourceDirectory(this, NULL, root_fullpath.get_basename());
return _root->scan(_path, key_filename);
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::get_root
// Access: Public
// Description: Returns the root directory of the hierarchy.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
get_root() const {
return _root;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::find_directory
// Access: Public
// Description: Returns the source directory that corresponds to the
// given path, or NULL if there is no such directory in
// the source tree.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
find_directory(const string &path) {
string root_fullpath = get_root_fullpath();
string fullpath = get_actual_fullpath(path);
// path is a subdirectory within the source hierarchy if and only if
// root_fullpath is an initial prefix of fullpath.
if (root_fullpath.length() > fullpath.length() ||
fullpath.substr(0, root_fullpath.length()) != root_fullpath) {
// Nope!
return (CVSSourceDirectory *)NULL;
}
// The relative name is the part of fullpath not in root_fullpath.
string relpath = fullpath.substr(root_fullpath.length());
return _root->find_relpath(relpath);
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::find_relpath
// Access: Public
// Description: Returns the source directory that corresponds to the
// given relative path from the root, or NULL if there
// is no match. The relative path may or may not
// include the name of the root directory itself.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
find_relpath(const string &relpath) {
CVSSourceDirectory *result = _root->find_relpath(relpath);
if (result != (CVSSourceDirectory *)NULL) {
return result;
}
// Check for the root dirname at the front of the path, and remove
// it if it's there.
size_t slash = relpath.find('/');
string first = relpath.substr(0, slash);
string rest;
if (slash != string::npos) {
rest = relpath.substr(slash + 1);
}
if (first == _root->get_dirname()) {
return _root->find_relpath(rest);
}
return (CVSSourceDirectory *)NULL;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::find_dirname
// Access: Public
// Description: Returns the source directory that corresponds to the
// given local directory name, or NULL if there
// is no match.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
find_dirname(const string &dirname) {
return _root->find_dirname(dirname);
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::choose_directory
// Access: Public
// Description: Determines where an externally referenced model file
// of the indicated name should go. It does this by
// looking for an existing model file of the same name;
// if a matching model is not found, or if multiple
// matching files are found, prompts the user for the
// directory, or uses suggested_dir.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
choose_directory(const string &filename, CVSSourceDirectory *suggested_dir,
bool force, bool interactive) {
static Directories empty_dirs;
Filenames::const_iterator fi;
fi = _filenames.find(filename);
if (fi != _filenames.end()) {
// The filename already exists somewhere.
const Directories &dirs = (*fi).second;
return prompt_user(filename, suggested_dir, dirs,
force, interactive);
}
// Now we have to prompt the user for a suitable place to put it.
return prompt_user(filename, suggested_dir, empty_dirs,
force, interactive);
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::get_root_fullpath
// Access: Public
// Description: Returns the full path from the root to the top of
// the source hierarchy.
////////////////////////////////////////////////////////////////////
string CVSSourceTree::
get_root_fullpath() {
nassertr(!_path.empty(), string());
if (!_got_root_fullpath) {
_root_fullpath = get_actual_fullpath(_path);
_got_root_fullpath = true;
}
return _root_fullpath;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::get_root_dirname
// Access: Public
// Description: Returns the local directory name of the root of the
// tree.
////////////////////////////////////////////////////////////////////
string CVSSourceTree::
get_root_dirname() const {
nassertr(_root != (CVSSourceDirectory *)NULL, string());
return _root->get_dirname();
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::add_file
// Access: Public
// Description: Adds a new file to the set of known files. This is
// normally called from CVSSourceDirectory::scan() and
// should not be called directly by the user.
////////////////////////////////////////////////////////////////////
void CVSSourceTree::
add_file(const string &filename, CVSSourceDirectory *dir) {
_filenames[filename].push_back(dir);
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::temp_chdir
// Access: Public, Static
// Description: Temporarily changes the current directory to the
// named path. Returns true on success, false on
// failure. Call restore_cwd() to restore to the
// original directory later.
////////////////////////////////////////////////////////////////////
bool CVSSourceTree::
temp_chdir(const string &path) {
// We have to call this first to guarantee that we have already
// determined our starting directory.
get_start_fullpath();
if (chdir(path.c_str()) < 0) {
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::restore_cwd
// Access: Public, Static
// Description: Restores the current directory after changing it from
// temp_chdir().
////////////////////////////////////////////////////////////////////
void CVSSourceTree::
restore_cwd() {
string start_fullpath = get_start_fullpath();
if (chdir(start_fullpath.c_str()) < 0) {
// Hey! We can't get back to the directory we started from!
perror(start_fullpath.c_str());
cerr << "Can't continue, aborting.\n";
exit(1);
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::prompt_user
// Access: Private
// Description: Prompts the user, if necessary, to choose a directory
// to import the given file into.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
prompt_user(const string &filename, CVSSourceDirectory *suggested_dir,
const CVSSourceTree::Directories &dirs,
bool force, bool interactive) {
if (dirs.size() == 1) {
// The file already exists in exactly one place.
if (interactive) {
return dirs[0];
}
CVSSourceDirectory *result = ask_existing(filename, dirs[0]);
if (result != (CVSSourceDirectory *)NULL) {
return result;
}
} else if (dirs.size() > 1) {
// The file already exists in multiple places.
if (force && !interactive) {
return dirs[0];
}
CVSSourceDirectory *result = ask_existing(filename, dirs, suggested_dir);
if (result != (CVSSourceDirectory *)NULL) {
return result;
}
}
// The file does not already exist, or the user declined to replace
// an existing file.
if (force && !interactive) {
return suggested_dir;
}
if (find(dirs.begin(), dirs.end(), suggested_dir) == dirs.end()) {
CVSSourceDirectory *result = ask_new(filename, suggested_dir);
if (result != (CVSSourceDirectory *)NULL) {
return result;
}
}
// Ask the user where the damn thing should go.
return ask_any(filename);
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::ask_existing
// Access: Private
// Description: Asks the user if he wants to replace an existing
// file.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
ask_existing(const string &filename, CVSSourceDirectory *dir) {
while (true) {
nout << filename << " found in tree at "
<< dir->get_path() + "/" + filename << ".\n";
string result = prompt("Overwrite this file (y/n)? ");
nassertr(!result.empty(), (CVSSourceDirectory *)NULL);
if (result.size() == 1) {
if (tolower(result[0]) == 'y') {
return dir;
} else if (tolower(result[0]) == 'n') {
return NULL;
}
}
nout << "*** Invalid response: " << result << "\n\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::ask_existing
// Access: Private
// Description: Asks the user which of several existing files he
// wants to replace.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
ask_existing(const string &filename, const CVSSourceTree::Directories &dirs,
CVSSourceDirectory *suggested_dir) {
while (true) {
nout << filename << " found in tree at more than one place:\n";
bool any_suggested = false;
for (int i = 0; i < (int)dirs.size(); i++) {
nout << " " << (i + 1) << ". "
<< dirs[i]->get_path() + "/" + filename << "\n";
if (dirs[i] == suggested_dir) {
any_suggested = true;
}
}
int next_option = dirs.size() + 1;
int suggested_option = -1;
if (!any_suggested) {
suggested_option = next_option;
next_option++;
nout << "\n" << suggested_option
<< ". create "
<< suggested_dir->get_path() + "/" + filename
<< "\n";
}
int other_option = next_option;
nout << other_option << ". Other\n";
string result = prompt("Choose an option: ");
nassertr(!result.empty(), (CVSSourceDirectory *)NULL);
const char *nptr = result.c_str();
char *endptr;
int option = strtol(nptr, &endptr, 10);
if (*endptr == '\0') {
if (option >= 1 && option <= (int)dirs.size()) {
return dirs[option - 1];
} else if (option == suggested_option) {
return suggested_dir;
} else if (option == other_option) {
return NULL;
}
}
nout << "*** Invalid response: " << result << "\n\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::ask_new
// Access: Private
// Description: Asks the user if he wants to replace an existing
// file.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
ask_new(const string &filename, CVSSourceDirectory *dir) {
while (true) {
nout << filename << " will be created at "
<< dir->get_path() + "/" + filename << ".\n";
string result = prompt("Create this file (y/n)? ");
nassertr(!result.empty(), (CVSSourceDirectory *)NULL);
if (result.size() == 1) {
if (tolower(result[0]) == 'y') {
return dir;
} else if (tolower(result[0]) == 'n') {
return NULL;
}
}
nout << "*** Invalid response: " << result << "\n\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::ask_any
// Access: Private
// Description: Asks the user to type in the name of the directory in
// which to store the file.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSSourceTree::
ask_any(const string &filename) {
while (true) {
string result =
prompt("Enter the name of the directory to copy " + filename + " to: ");
nassertr(!result.empty(), (CVSSourceDirectory *)NULL);
// The user might enter a fully-qualified path to the directory,
// or a relative path from the root (with or without the root's
// dirname), or the dirname of the particular directory.
CVSSourceDirectory *dir = find_directory(result);
if (dir == (CVSSourceDirectory *)NULL) {
dir = find_relpath(result);
}
if (dir == (CVSSourceDirectory *)NULL) {
dir = find_dirname(result);
}
if (dir != (CVSSourceDirectory *)NULL) {
return dir;
}
nout << "*** Not a valid directory name: " << result << "\n\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::prompt
// Access: Private
// Description: Issues a prompt to the user and waits for a typed
// response. Returns the response (which will not be
// empty).
////////////////////////////////////////////////////////////////////
string CVSSourceTree::
prompt(const string &message) {
nout << flush;
while (true) {
cerr << message << flush;
string response;
getline(cin, response);
// Remove leading and trailing whitespace.
size_t p = 0;
while (p < response.length() && isspace(response[p])) {
p++;
}
size_t q = response.length();
while (q > p && isspace(response[q - 1])) {
q--;
}
if (q > p) {
return response.substr(p, q - p);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::get_actual_fullpath
// Access: Private, Static
// Description: Determines the actual full path from the root to the
// named directory. It does this essentially by cd'ing
// to the directory and doing pwd, then cd'ing back.
// Returns the empty string if the directory is invalid
// or cannot be cd'ed into.
////////////////////////////////////////////////////////////////////
string CVSSourceTree::
get_actual_fullpath(const string &path) {
if (!temp_chdir(path)) {
return string();
}
string cwd = get_cwd();
restore_cwd();
return cwd;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::get_start_fullpath
// Access: Private, Static
// Description: Returns the full path from the root to the directory
// in which the user started the program.
////////////////////////////////////////////////////////////////////
string CVSSourceTree::
get_start_fullpath() {
if (!_got_start_fullpath) {
_start_fullpath = get_cwd();
_got_start_fullpath = true;
}
return _start_fullpath;
}
////////////////////////////////////////////////////////////////////
// Function: CVSSourceTree::get_cwd
// Access: Private, Static
// Description: Calls the system getcwd(), automatically allocating a
// large enough string.
////////////////////////////////////////////////////////////////////
string CVSSourceTree::
get_cwd() {
static size_t bufsize = 1024;
static char *buffer = NULL;
if (buffer == (char *)NULL) {
buffer = new char[bufsize];
}
while (getcwd(buffer, bufsize) == (char *)NULL) {
if (errno != ERANGE) {
perror("getcwd");
return string();
}
delete[] buffer;
bufsize = bufsize * 2;
buffer = new char[bufsize];
nassertr(buffer != (char *)NULL, string());
}
return string(buffer);
}

View File

@ -0,0 +1,81 @@
// Filename: cvsSourceTree.h
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
#ifndef CVSSOURCETREE_H
#define CVSSOURCETREE_H
#include <pandatoolbase.h>
#include <vector>
#include <map>
class CVSSourceDirectory;
////////////////////////////////////////////////////////////////////
// Class : CVSSourceTree
// Description : This represents the root of the tree of source
// directory files.
////////////////////////////////////////////////////////////////////
class CVSSourceTree {
public:
CVSSourceTree();
~CVSSourceTree();
void set_root(const string &root_path);
bool scan(const string &key_filename);
CVSSourceDirectory *get_root() const;
CVSSourceDirectory *find_directory(const string &path);
CVSSourceDirectory *find_relpath(const string &relpath);
CVSSourceDirectory *find_dirname(const string &dirname);
CVSSourceDirectory *choose_directory(const string &filename,
CVSSourceDirectory *suggested_dir,
bool force, bool interactive);
string get_root_fullpath();
string get_root_dirname() const;
static bool temp_chdir(const string &path);
static void restore_cwd();
public:
void add_file(const string &filename, CVSSourceDirectory *dir);
private:
typedef vector<CVSSourceDirectory *> Directories;
CVSSourceDirectory *
prompt_user(const string &filename, CVSSourceDirectory *suggested_dir,
const Directories &dirs, bool force, bool interactive);
CVSSourceDirectory *ask_existing(const string &filename,
CVSSourceDirectory *dir);
CVSSourceDirectory *ask_existing(const string &filename,
const Directories &dirs,
CVSSourceDirectory *suggested_dir);
CVSSourceDirectory *ask_new(const string &filename, CVSSourceDirectory *dir);
CVSSourceDirectory *ask_any(const string &filename);
string prompt(const string &message);
static string get_actual_fullpath(const string &path);
static string get_start_fullpath();
static string get_cwd();
private:
string _path;
CVSSourceDirectory *_root;
typedef map<string, Directories> Filenames;
Filenames _filenames;
static bool _got_start_fullpath;
static string _start_fullpath;
bool _got_root_fullpath;
string _root_fullpath;
};
#endif

View File

@ -0,0 +1,94 @@
// Filename: testCopy.cxx
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
#include "testCopy.h"
#include "cvsSourceDirectory.h"
////////////////////////////////////////////////////////////////////
// Function: TestCopy::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
TestCopy::
TestCopy() {
set_program_description
("This program copies one or more files into a CVS source hierarchy. "
"Rather than copying the named files immediately into the current "
"directory, it first scans the entire source hierarchy, identifying all "
"the already-existing files. If the named file to copy matches the "
"name of an already-existing file in the current directory or elsewhere "
"in the hierarchy, that file is overwritten.\n\n"
"This is primarily useful as a test program for libcvscopy.");
}
////////////////////////////////////////////////////////////////////
// Function: TestCopy::run
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void TestCopy::
run() {
SourceFiles::iterator fi;
for (fi = _source_files.begin(); fi != _source_files.end(); ++fi) {
CVSSourceDirectory *dest = import(*fi, 0, _model_dir);
if (dest == (CVSSourceDirectory *)NULL) {
exit(1);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: TestCopy::copy_file
// Access: Protected, Virtual
// Description: Called by import() if the timestamps indicate that a
// file needs to be copied. This does the actual copy
// of a file from source to destination. If new_file is
// true, then dest does not already exist.
////////////////////////////////////////////////////////////////////
bool TestCopy::
copy_file(Filename source, Filename dest,
CVSSourceDirectory *dir, int type, bool new_file) {
source.set_binary();
dest.set_binary();
ifstream in;
ofstream out;
if (!source.open_read(in)) {
cerr << "Cannot read " << source << "\n";
return false;
}
if (!dest.open_write(out)) {
cerr << "Cannot write " << dest << "\n";
return false;
}
int c;
c = in.get();
while (!in.eof() && !in.fail() && !out.fail()) {
out.put(c);
c = in.get();
}
if (in.fail()) {
cerr << "Error reading " << source << "\n";
return false;
}
if (out.fail()) {
cerr << "Error writing " << dest << "\n";
return false;
}
return true;
}
int main(int argc, char *argv[]) {
TestCopy prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,29 @@
// Filename: testCopy.h
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
#ifndef TESTCOPY_H
#define TESTCOPY_H
#include <pandatoolbase.h>
#include "cvsCopy.h"
////////////////////////////////////////////////////////////////////
// Class : TestCopy
// Description : A program to copy ordinary files into the cvs tree.
// Mainly to test CVSCopy.
////////////////////////////////////////////////////////////////////
class TestCopy : public CVSCopy {
public:
TestCopy();
void run();
protected:
virtual bool copy_file(Filename source, Filename dest,
CVSSourceDirectory *dir, int type, bool new_file);
};
#endif

View File

@ -73,6 +73,7 @@ ProgramBase() {
_next_sequence = 0;
_sorted_options = false;
_last_newline = false;
_got_terminal_width = false;
_got_option_indent = false;
@ -171,7 +172,8 @@ show_text(const string &prefix, int indent_width, string text) {
// This is correct! It goes go to cerr, not to nout. Sending it to
// nout would be cyclic, since nout is redefined to map back through
// this function.
format_text(cerr, prefix, indent_width, text, _terminal_width);
format_text(cerr, _last_newline,
prefix, indent_width, text, _terminal_width);
}
////////////////////////////////////////////////////////////////////
@ -679,12 +681,19 @@ handle_help_option(const string &, const string &, void *) {
//
// An embedded newline character ('\n') forces a line
// break, while an embedded carriage-return character
// ('\r') marks a paragraph break, which is usually
// printed as a blank line. Redundant newline and
// carriage-return characters are generally ignored.
// ('\r'), or two or more consecutive newlines, marks a
// paragraph break, which is usually printed as a blank
// line. Redundant newline and carriage-return
// characters are generally ignored.
//
// The flag last_newline should be initialized to false
// for the first call to format_text, and then preserved
// for future calls; it tracks the state of trailing
// newline characters between calls so we can correctly
// identify doubled newlines.
////////////////////////////////////////////////////////////////////
void ProgramBase::
format_text(ostream &out,
format_text(ostream &out, bool &last_newline,
const string &prefix, int indent_width,
const string &text, int line_width) {
indent_width = min(indent_width, line_width - 20);
@ -705,7 +714,9 @@ format_text(ostream &out,
// Skip any initial whitespace and newlines.
while (p < text.length() && isspace(text[p])) {
if (text[p] == '\r') {
if (text[p] == '\r' ||
(p > 0 && text[p] == '\n' && text[p - 1] == '\n') ||
(p == 0 && text[p] == '\n' && last_newline)) {
if (!initial_break) {
// Here's an initial paragraph break, however.
out << "\n";
@ -724,6 +735,8 @@ format_text(ostream &out,
p++;
}
last_newline = (!text.empty() && text[text.length() - 1] == '\n');
while (p < text.length()) {
// Look for the paragraph or line break--the next newline
// character, if any.
@ -731,8 +744,11 @@ format_text(ostream &out,
bool is_paragraph_break = false;
if (par == string::npos) {
par = text.length();
/*
This shouldn't be necessary.
} else {
is_paragraph_break = (text[par] == '\r');
*/
}
indent(out, indent_amount);
@ -771,7 +787,8 @@ format_text(ostream &out,
// Skip additional whitespace between the lines.
while (p < text.length() && isspace(text[p])) {
if (text[p] == '\r') {
if (text[p] == '\r' ||
(p > 0 && text[p] == '\n' && text[p - 1] == '\n')) {
is_paragraph_break = true;
}
p++;
@ -780,6 +797,11 @@ format_text(ostream &out,
if (eol == par && is_paragraph_break) {
// Print the paragraph break as a blank line.
out << "\n";
if (p >= text.length()) {
// If we end on a paragraph break, don't try to insert a new
// one in the next pass.
last_newline = false;
}
}
indent_amount = indent_width;

View File

@ -68,7 +68,7 @@ protected:
bool handle_help_option(const string &opt, const string &arg, void *);
static void format_text(ostream &out,
static void format_text(ostream &out, bool &last_newline,
const string &prefix, int indent_width,
const string &text, int line_width);
@ -107,6 +107,7 @@ private:
typedef map<string, string> GotOptions;
GotOptions _got_options;
bool _last_newline;
int _terminal_width;
bool _got_terminal_width;
int _option_indent;