// // aegis - project change supervisor // Copyright (C) 2004-2009, 2011-2014 Peter Miller // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see // . // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include module_cvsroot::~module_cvsroot() { } module_cvsroot::module_cvsroot() { } void module_cvsroot::modified(server_ty *sp, string_ty *file_name, file_info_ty *, const input::pointer &) { // // Throw away the file contents the client is sending to us. We only // pretend to allow them to modify us. (Just closing it will take // care of it.) // server_m ( sp, "Module \"%s\" file \"%s\" modify ignored; please\n" "use Aegis project management commands instead.", name()->str_text, file_name->str_text ); } string_ty * module_cvsroot::calculate_canonical_name(void) const { // FIXME: memory leak return str_from_c("CVSROOT"); } static int sanitary_length(const nstring &s, int llen) { if (llen < 15) llen = 15; llen = 76 - 2 * llen; if (llen <= 0) return 0; const char *cp = s.c_str(); const char *cp_max = cp + llen; while (cp < cp_max && *cp && isprint((unsigned char)*cp)) ++cp; return (cp - s.c_str()); } static void checkout_modules_inner(string_list_ty *modules, project *pp) { // // Add the cannonical name of this project to the list // with a project description as a comment. // nstring proj = project_name_get(pp); nstring desc(project_brief_description_get(pp)); int desc_len = sanitary_length(desc, 20); string_ty *s = str_format ( "%-15s %-15s # %.*s\n", proj.c_str(), proj.c_str(), desc_len, desc.c_str() ); modules->push_back(s); str_free(s); // // check each change // add it to the list of it is being developed // recurse if it is an active branch // for (long k = 0; ; ++k) { long cn; change::pointer cp2; if (!project_change_nth(pp, k, &cn)) break; cp2 = change::create(pp, cn); cp2->bind_existing(); // active only if (cp2->is_a_branch()) { project *pp2 = pp->bind_branch(cp2); checkout_modules_inner(modules, pp2); pp2->free(); } else { if (cp2->is_being_developed()) { nstring ss = nstring::format ( "%s.C%3.3ld", project_name_get(pp).c_str(), cn ); nstring desc2 = cp2->brief_description_get(); int desc2_len = sanitary_length(desc2, ss.size()); string_ty *s2 = str_format ( "%-15s %-15s # %.*s\n", ss.c_str(), ss.c_str(), desc2_len, desc2.c_str() ); modules->push_back(s2); str_free(s2); } } } // do NOT free "lp" } void module_cvsroot::checkout_modules(server_ty *sp) { string_list_ty toplevel; size_t j; string_ty *server_side; string_ty *client_side; string_ty *version; int mode; string_ty *s; // // The modules file is line oriented. In its simplest form each line // contains the name of the module, whitespace, and the directory where // the module resides. The directory is a path relative to $CVSROOT. // The last four lines in the example above are examples of such lines. // // The modules file records your definitions of names for collections // of source code. cvs will use these definitions if you use cvs to // update the modules file (use normal commands like add, commit, etc). // // The modules file may contain blank lines and comments (lines beginning // with #) as well as module definitions. Long lines can be continued // on the next line by specifying a backslash (\) as the last character // on the line. // // There are three basic types of modules: alias modules, regular // modules, and ampersand modules. The difference between them is the // way that they map files in the repository to files in the working // directory. In all of the following examples, the top-level repository // contains a directory called first-dir, which contains two files, // file1 and file2, and a directory sdir. first-dir/sdir contains a // file sfile. // // // Alias modules // // Alias modules are the simplest kind of module: // // mname -a aliases // This represents the simplest way of defining a module mname. // The -a flags the definition as a simple alias: cvs will treat // any use of mname (as a command argument) as if the list of names // aliases had been specified instead. aliases may contain either // other module names or paths. When you use paths in aliases, // checkout creates all intermediate directories in the working // directory, just as if the path had been specified explicitly in // the cvs arguments. // // For example, if the modules file contains: // // amodule -a first-dir // // then the following two commands are equivalent: // // $ cvs co amodule // $ cvs co first-dir // // and they each would provide output such as: // // cvs checkout: Updating first-dir // U first-dir/file1 // U first-dir/file2 // cvs checkout: Updating first-dir/sdir // U first-dir/sdir/sfile // // // Regular modules // // mname [ options ] dir [ files ] // In the simplest case, this form of module definition reduces // to mname dir. This defines all the files in directory dir // as module mname. dir is a relative path (from $CVSROOT) to a // directory of source in the source repository. In this case, on // checkout, a single directory called mname is created as a working // directory; no intermediate directory levels are used by default, // even if dir was a path involving several directory levels. // // For example, if a module is defined by: // // regmodule first-dir // // then regmodule will contain the files from first-dir: // // $ cvs co regmodule // cvs checkout: Updating regmodule // U regmodule/file1 // U regmodule/file2 // cvs checkout: Updating regmodule/sdir // U regmodule/sdir/sfile // $ // // By explicitly specifying files in the module definition after dir, you // can select particular files from directory dir. Here is an example: // // regfiles first-dir/sdir sfile // // With this definition, getting the regfiles module will create a // single working directory regfiles containing the file listed, which // comes from a directory deeper in the cvs source repository: // // $ cvs co regfiles // U regfiles/sfile // $ // // // Ampersand modules // // A module definition can refer to other modules by including &module // in its definition. // // mname [ options ] &module // // Then getting the module creates a subdirectory for each such module, // in the directory containing the module. For example, if modules // contains // // ampermod &first-dir // // then a checkout will create an ampermod directory which contains // a directory called first-dir, which in turns contains all the // directories and files which live there. For example, the command // // $ cvs co ampermod // // will create the following files: // // ampermod/first-dir/file1 // ampermod/first-dir/file2 // ampermod/first-dir/sdir/sfile // // There is one quirk/bug: the messages that cvs prints omit the // ampermod, and thus do not correctly display the location to which // it is checking out the files: // // $ cvs co ampermod // cvs checkout: Updating first-dir // U first-dir/file1 // U first-dir/file2 // cvs checkout: Updating first-dir/sdir // U first-dir/sdir/sfile // $ // // Do not rely on this buggy behavior; it may get fixed in a future // release of cvs. // // // Excluding directories // // An alias module may exclude particular directories from other modules // by using an exclamation mark (!) before the name of each directory // to be excluded. // // For example, if the modules file contains: // // exmodule -a !first-dir/sdir first-dir // // then checking out the module exmodule will check out everything in // first-dir except any files in the subdirectory first-dir/sdir. // // Note that the "!first-dir/sdir" sometimes must be listed before // "first-dir". That seems like a probable bug, in which case perhaps // it should be fixed (to allow either order) rather than documented. // See modules4 in testsuite. // // // Module options // // Either regular modules or ampersand modules can contain options, // which supply additional information concerning the module. // // -d name // Name the working directory something other than the module name. // // -e prog // Specify a program prog to run whenever files in a module are // exported. prog runs with a single argument, the module name. // // -o prog // Specify a program prog to run whenever files in a module are // checked out. prog runs with a single argument, the module name. // See Module program options for information on how prog is called. // // -s status // Assign a status to the module. When the module file is printed // with cvs checkout -s the modules are sorted according to primarily // module status, and secondarily according to the module name. // This option has no other meaning. You can use this option for // several things besides status: for instance, list the person // that is responsible for this module. // // -t prog // Specify a program prog to run whenever files in a module are // tagged with rtag. prog runs with two arguments: the module name // and the symbolic tag specified to rtag. It is not run when tag // is executed. Generally you will find that taginfo is a better // solution (user-defined logging). // // You should also see Module program options about how the "program // options" programs are run. // // // Module program options // // For checkout, rtag, and export, the program is server-based, and as // such the following applies:- // // If using remote access methods (pserver, ext, etc.), cvs will execute // this program on the server from a temporary directory. The path is // searched for this program. // // If using "local access" (on a local or remote NFS file system, i.e. // repository set just to a path), the program will be executed from // the newly checked-out tree, if found there, or alternatively searched // for in the path if not. // // The programs are all run after the operation has effectively // completed. // // // Put the CVSROOT module in the list. // string_list_ty modules; s = str_from_c ( "#\n" "# This file is generated. You can not commit it.\n" "# You must perform Aegis administration using Aegis commands.\n" "#\n" "CVSROOT CVSROOT\n" ); modules.push_back(s); str_free(s); // // Walk the project tree looking for active branches and // being-developed change sets. // gonzo_project_list(&toplevel); for (j = 0; j < toplevel.size(); ++j) { string_ty *prjname; project *pp; int err; prjname = toplevel[j]; pp = project_alloc(prjname); pp->bind_existing(); // // watch out for permissions // (returns errno of attempt to read project state) // err = project_is_readable(pp); // // Recurse into readable branch trees. // if (!err) checkout_modules_inner(&modules, pp); else modules.push_back(project_name_get(pp).get_ref_copy()); pp->free(); } toplevel.clear(); // // Also extract the project aliases. // gonzo_alias_list(&toplevel); for (j = 0; j < toplevel.size(); ++j) { string_ty *alias_name; string_ty *other; alias_name = toplevel[j]; other = gonzo_alias_to_actual(alias_name); assert(other); if (!other) continue; s = str_format("%-12s -a %s\n", alias_name->str_text, other->str_text); modules.push_back(s); str_free(s); } // // sort the list of names // (C locale) // // Project names look a lot like versions strings (indeed, // the tail ends *are* version strings) so sort them as such. // modules.sort_version(); // // Now build a string based on the list of module names. // s = modules.unsplit(""); // // Now queue it all to be sent to the client. // server_side = str_from_c("CVSROOT/modules"); client_side = server_directory_calc_client_side(sp, server_side); input::pointer ip = input_string::create(nstring(s)); str_free(s); mode = 0444; version = fake_version_now(); server_mkdir_above(sp, client_side, server_side); server_updating_verbose(sp, client_side); server_response_queue ( sp, new response_created(client_side, server_side, ip, mode, version) ); str_free(version); str_free(client_side); str_free(server_side); } bool module_cvsroot::update(server_ty *sp, string_ty *, string_ty *, const options &) { // // FIXME: the client_side and serve_side COULD be refering to // individual files, not just the whole directory. // // checkoutlist // config // cvsignore // cvswrappers // history // loginfo // modules checkout_modules(sp); // rcsinfo // verifymsg // // Report success. // server_ok(sp); return true; } void module_cvsroot::groan(server_ty *sp, const char *request_name) { server_error ( sp, "%s: You can not administer Aegis from this interface, " "you must use Aegis commands directly.", request_name ); } bool module_cvsroot::checkin(server_ty *sp, string_ty *, string_ty *) { groan(sp, "ci"); return false; } bool module_cvsroot::add(server_ty *sp, string_ty *, string_ty *, const options &) { groan(sp, "add"); return false; } bool module_cvsroot::remove(server_ty *sp, string_ty *, string_ty *, const options &) { groan(sp, "remove"); return false; } // vim: set ts=8 sw=4 et :