1 //
2 //      aegis - project change supervisor
3 //      Copyright (C) 2004-2009, 2011, 2012 Peter Miller
4 //
5 //      This program is free software; you can redistribute it and/or modify
6 //      it under the terms of the GNU General Public License as published by
7 //      the Free Software Foundation; either version 3 of the License, or
8 //      (at your option) any later version.
9 //
10 //      This program is distributed in the hope that it will be useful,
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //      GNU General Public License for more details.
14 //
15 //      You should have received a copy of the GNU General Public License
16 //      along with this program. If not, see
17 //      <http://www.gnu.org/licenses/>.
18 //
19 
20 #include <common/ac/assert.h>
21 #include <common/ac/ctype.h>
22 
23 #include <common/error.h>
24 #include <common/now.h>
25 #include <libaegis/change.h>
26 #include <libaegis/change/branch.h>
27 #include <libaegis/gonzo.h>
28 #include <libaegis/input/string.h>
29 #include <libaegis/project.h>
30 #include <libaegis/project/history.h>
31 
32 #include <aecvsserver/fake_version.h>
33 #include <aecvsserver/file_info.h>
34 #include <aecvsserver/module/cvsroot.h>
35 #include <aecvsserver/response/clear_sticky.h>
36 #include <aecvsserver/response/clearstatdir.h>
37 #include <aecvsserver/response/created.h>
38 #include <aecvsserver/server.h>
39 
40 
~module_cvsroot()41 module_cvsroot::~module_cvsroot()
42 {
43 }
44 
45 
module_cvsroot()46 module_cvsroot::module_cvsroot()
47 {
48 }
49 
50 
51 void
modified(server_ty * sp,string_ty * file_name,file_info_ty *,input &)52 module_cvsroot::modified(server_ty *sp, string_ty *file_name, file_info_ty *,
53     input &)
54 {
55     //
56     // Throw away the file contents the client is sending to us.  We only
57     // pretend to allow them to modify us.  (Just closing it will take
58     // care of it.)
59     //
60     server_m
61     (
62         sp,
63         "Module \"%s\" file \"%s\" modify ignored; please\n"
64             "use Aegis project management commands instead.",
65         name()->str_text,
66         file_name->str_text
67     );
68 }
69 
70 
71 string_ty *
calculate_canonical_name() const72 module_cvsroot::calculate_canonical_name()
73     const
74 {
75     // FIXME: memory leak
76     return str_from_c("CVSROOT");
77 }
78 
79 
80 static int
sanitary_length(const nstring & s,int llen)81 sanitary_length(const nstring &s, int llen)
82 {
83     if (llen < 15)
84         llen = 15;
85     llen = 76 - 2 * llen;
86     if (llen <= 0)
87         return 0;
88     const char *cp = s.c_str();
89     const char *cp_max = cp + llen;
90     while (cp < cp_max && *cp && isprint((unsigned char)*cp))
91         ++cp;
92     return (cp - s.c_str());
93 }
94 
95 
96 static void
checkout_modules_inner(string_list_ty * modules,project * pp)97 checkout_modules_inner(string_list_ty *modules, project *pp)
98 {
99     //
100     // Add the cannonical name of this project to the list
101     // with a project description as a comment.
102     //
103     nstring proj = project_name_get(pp);
104     nstring desc(project_brief_description_get(pp));
105     int desc_len = sanitary_length(desc, 20);
106     string_ty *s =
107         str_format
108         (
109             "%-15s %-15s # %.*s\n",
110             s->str_text,
111             s->str_text,
112             desc_len,
113             desc.c_str()
114         );
115     modules->push_back(s);
116     str_free(s);
117 
118     //
119     // check each change
120     // add it to the list of it is being developed
121     // recurse if it is an active branch
122     //
123     for (long k = 0; ; ++k)
124     {
125         long            cn;
126         change::pointer cp2;
127 
128         if (!project_change_nth(pp, k, &cn))
129             break;
130         cp2 = change_alloc(pp, cn);
131         change_bind_existing(cp2);
132         // active only
133         if (cp2->is_a_branch())
134         {
135             project *pp2 = pp->bind_branch(cp2);
136             checkout_modules_inner(modules, pp2);
137             project_free(pp2);
138         }
139         else
140         {
141             if (cp2->is_being_developed())
142             {
143                 nstring ss =
144                     nstring::format
145                     (
146                         "%s.C%3.3ld",
147                         project_name_get(pp).c_str(),
148                         cn
149                     );
150                 nstring desc2 = cp2->brief_description_get();
151                 int desc2_len = sanitary_length(desc2, ss.size());
152                 string_ty *s2 =
153                     str_format
154                     (
155                         "%-15s %-15s # %.*s\n",
156                         ss.c_str(),
157                         ss.c_str(),
158                         desc2_len,
159                         desc2.c_str()
160                     );
161                 modules->push_back(s2);
162                 str_free(s2);
163             }
164             change_free(cp2);
165         }
166     }
167     // do NOT free "lp"
168     // do NOT free "cp"
169 }
170 
171 
172 void
checkout_modules(server_ty * sp)173 module_cvsroot::checkout_modules(server_ty *sp)
174 {
175     string_list_ty  toplevel;
176     size_t          j;
177     string_ty       *server_side;
178     string_ty       *client_side;
179     string_ty       *version;
180     int             mode;
181     string_ty       *s;
182 
183     //
184     // The modules file is line oriented.  In its simplest form each line
185     // contains the name of the module, whitespace, and the directory where
186     // the module resides.  The directory is a path relative to $CVSROOT.
187     // The last four lines in the example above are examples of such lines.
188     //
189     // The modules file records your definitions of names for collections
190     // of source code.  cvs will use these definitions if you use cvs to
191     // update the modules file (use normal commands like add, commit, etc).
192     //
193     // The modules file may contain blank lines and comments (lines beginning
194     // with #) as well as module definitions.  Long lines can be continued
195     // on the next line by specifying a backslash (\) as the last character
196     // on the line.
197     //
198     // There are three basic types of modules: alias modules, regular
199     // modules, and ampersand modules.  The difference between them is the
200     // way that they map files in the repository to files in the working
201     // directory.  In all of the following examples, the top-level repository
202     // contains a directory called first-dir, which contains two files,
203     // file1 and file2, and a directory sdir.  first-dir/sdir contains a
204     // file sfile.
205     //
206     //
207     // Alias modules
208     //
209     // Alias modules are the simplest kind of module:
210     //
211     // mname -a aliases
212     //     This represents the simplest way of defining a module mname.
213     //     The -a flags the definition as a simple alias: cvs will treat
214     //     any use of mname (as a command argument) as if the list of names
215     //     aliases had been specified instead.  aliases may contain either
216     //     other module names or paths.  When you use paths in aliases,
217     //     checkout creates all intermediate directories in the working
218     //     directory, just as if the path had been specified explicitly in
219     //     the cvs arguments.
220     //
221     // For example, if the modules file contains:
222     //
223     //     amodule -a first-dir
224     //
225     // then the following two commands are equivalent:
226     //
227     //     $ cvs co amodule
228     //     $ cvs co first-dir
229     //
230     // and they each would provide output such as:
231     //
232     //     cvs checkout: Updating first-dir
233     //     U first-dir/file1
234     //     U first-dir/file2
235     //     cvs checkout: Updating first-dir/sdir
236     //     U first-dir/sdir/sfile
237     //
238     //
239     // Regular modules
240     //
241     // mname [ options ] dir [ files ]
242     //     In the simplest case, this form of module definition reduces
243     //     to mname dir.  This defines all the files in directory dir
244     //     as module mname.  dir is a relative path (from $CVSROOT) to a
245     //     directory of source in the source repository.  In this case, on
246     //     checkout, a single directory called mname is created as a working
247     //     directory; no intermediate directory levels are used by default,
248     //     even if dir was a path involving several directory levels.
249     //
250     // For example, if a module is defined by:
251     //
252     //     regmodule first-dir
253     //
254     // then regmodule will contain the files from first-dir:
255     //
256     //     $ cvs co regmodule
257     //     cvs checkout: Updating regmodule
258     //     U regmodule/file1
259     //     U regmodule/file2
260     //     cvs checkout: Updating regmodule/sdir
261     //     U regmodule/sdir/sfile
262     //     $
263     //
264     // By explicitly specifying files in the module definition after dir, you
265     // can select particular files from directory dir.  Here is an example:
266     //
267     //     regfiles first-dir/sdir sfile
268     //
269     // With this definition, getting the regfiles module will create a
270     // single working directory regfiles containing the file listed, which
271     // comes from a directory deeper in the cvs source repository:
272     //
273     //     $ cvs co regfiles
274     //     U regfiles/sfile
275     //     $
276     //
277     //
278     // Ampersand modules
279     //
280     // A module definition can refer to other modules by including &module
281     // in its definition.
282     //
283     //     mname [ options ] &module
284     //
285     // Then getting the module creates a subdirectory for each such module,
286     // in the directory containing the module.  For example, if modules
287     // contains
288     //
289     //     ampermod &first-dir
290     //
291     // then a checkout will create an ampermod directory which contains
292     // a directory called first-dir, which in turns contains all the
293     // directories and files which live there.  For example, the command
294     //
295     //     $ cvs co ampermod
296     //
297     // will create the following files:
298     //
299     //     ampermod/first-dir/file1
300     //     ampermod/first-dir/file2
301     //     ampermod/first-dir/sdir/sfile
302     //
303     // There is one quirk/bug: the messages that cvs prints omit the
304     // ampermod, and thus do not correctly display the location to which
305     // it is checking out the files:
306     //
307     //     $ cvs co ampermod
308     //     cvs checkout: Updating first-dir
309     //     U first-dir/file1
310     //     U first-dir/file2
311     //     cvs checkout: Updating first-dir/sdir
312     //     U first-dir/sdir/sfile
313     //     $
314     //
315     // Do not rely on this buggy behavior; it may get fixed in a future
316     // release of cvs.
317     //
318     //
319     // Excluding directories
320     //
321     // An alias module may exclude particular directories from other modules
322     // by using an exclamation mark (!)  before the name of each directory
323     // to be excluded.
324     //
325     // For example, if the modules file contains:
326     //
327     //     exmodule -a !first-dir/sdir first-dir
328     //
329     // then checking out the module exmodule will check out everything in
330     // first-dir except any files in the subdirectory first-dir/sdir.
331     //
332     // Note that the "!first-dir/sdir" sometimes must be listed before
333     // "first-dir".  That seems like a probable bug, in which case perhaps
334     // it should be fixed (to allow either order) rather than documented.
335     // See modules4 in testsuite.
336     //
337     //
338     // Module options
339     //
340     // Either regular modules or ampersand modules can contain options,
341     // which supply additional information concerning the module.
342     //
343     // -d name
344     //     Name the working directory something other than the module name.
345     //
346     // -e prog
347     //     Specify a program prog to run whenever files in a module are
348     //     exported.  prog runs with a single argument, the module name.
349     //
350     // -o prog
351     //     Specify a program prog to run whenever files in a module are
352     //     checked out.  prog runs with a single argument, the module name.
353     //     See Module program options for information on how prog is called.
354     //
355     // -s status
356     //     Assign a status to the module.  When the module file is printed
357     //     with cvs checkout -s the modules are sorted according to primarily
358     //     module status, and secondarily according to the module name.
359     //     This option has no other meaning.  You can use this option for
360     //     several things besides status: for instance, list the person
361     //     that is responsible for this module.
362     //
363     // -t prog
364     //     Specify a program prog to run whenever files in a module are
365     //     tagged with rtag.  prog runs with two arguments: the module name
366     //     and the symbolic tag specified to rtag.  It is not run when tag
367     //     is executed.  Generally you will find that taginfo is a better
368     //     solution (user-defined logging).
369     //
370     // You should also see Module program options about how the "program
371     // options" programs are run.
372     //
373     //
374     // Module program options
375     //
376     // For checkout, rtag, and export, the program is server-based, and as
377     // such the following applies:-
378     //
379     // If using remote access methods (pserver, ext, etc.), cvs will execute
380     // this program on the server from a temporary directory. The path is
381     // searched for this program.
382     //
383     // If using "local access" (on a local or remote NFS file system, i.e.
384     // repository set just to a path), the program will be executed from
385     // the newly checked-out tree, if found there, or alternatively searched
386     // for in the path if not.
387     //
388     // The programs are all run after the operation has effectively
389     // completed.
390     //
391 
392     //
393     // Put the CVSROOT module in the list.
394     //
395     string_list_ty modules;
396     s =
397         str_from_c
398         (
399             "#\n"
400             "# This file is generated.  You can not commit it.\n"
401             "# You must perform Aegis administration using Aegis commands.\n"
402             "#\n"
403             "CVSROOT         CVSROOT\n"
404         );
405     modules.push_back(s);
406     str_free(s);
407 
408     //
409     // Walk the project tree looking for active branches and
410     // being-developed change sets.
411     //
412     gonzo_project_list(&toplevel);
413     for (j = 0; j < toplevel.nstrings; ++j)
414     {
415         string_ty       *prjname;
416         project *pp;
417         int             err;
418 
419         prjname = toplevel.string[j];
420         pp = project_alloc(prjname);
421         pp->bind_existing();
422 
423         //
424         // watch out for permissions
425         // (returns errno of attempt to read project state)
426         //
427         err = project_is_readable(pp);
428 
429         //
430         // Recurse into readable branch trees.
431         //
432         if (!err)
433             checkout_modules_inner(&modules, pp);
434         else
435             modules.push_back(project_name_get(pp).get_ref_copy());
436         project_free(pp);
437     }
438     toplevel.clear();
439 
440     //
441     // Also extract the project aliases.
442     //
443     gonzo_alias_list(&toplevel);
444     for (j = 0; j < toplevel.nstrings; ++j)
445     {
446         string_ty       *alias_name;
447         string_ty       *other;
448 
449         alias_name = toplevel.string[j];
450         other = gonzo_alias_to_actual(alias_name);
451         assert(other);
452         if (!other)
453             continue;
454         s = str_format("%-12s -a %s\n", alias_name->str_text, other->str_text);
455         modules.push_back(s);
456         str_free(s);
457     }
458 
459     //
460     // sort the list of names
461     // (C locale)
462     //
463     // Project names look a lot like versions strings (indeed,
464     // the tail ends *are* version strings) so sort them as such.
465     //
466     modules.sort_version();
467 
468     //
469     // Now build a string based on the list of module names.
470     //
471     s = modules.unsplit("");
472 
473     //
474     // Now queue it all to be sent to the client.
475     //
476     server_side = str_from_c("CVSROOT/modules");
477     client_side = server_directory_calc_client_side(sp, server_side);
478     input ip = new input_string(nstring(s));
479     str_free(s);
480     mode = 0444;
481     version = fake_version_now();
482     server_mkdir_above(sp, client_side, server_side);
483     server_updating_verbose(sp, client_side);
484     server_response_queue
485     (
486         sp,
487         new response_created(client_side, server_side, ip, mode, version)
488     );
489     str_free(version);
490     str_free(client_side);
491     str_free(server_side);
492 }
493 
494 
495 bool
update(server_ty * sp,string_ty *,string_ty *,const options &)496 module_cvsroot::update(server_ty *sp, string_ty *, string_ty *, const options &)
497 {
498     //
499     // FIXME: the client_side and serve_side COULD be refering to
500     // individual files, not just the whole directory.
501     //
502 
503     // checkoutlist
504     // config
505     // cvsignore
506     // cvswrappers
507     // history
508     // loginfo
509     // modules
510     checkout_modules(sp);
511     // rcsinfo
512     // verifymsg
513 
514     //
515     // Report success.
516     //
517     server_ok(sp);
518     return true;
519 }
520 
521 
522 void
groan(server_ty * sp,const char * request_name)523 module_cvsroot::groan(server_ty *sp, const char *request_name)
524 {
525     server_error
526     (
527         sp,
528         "%s: You can not administer Aegis from this interface, "
529             "you must use Aegis commands directly.",
530         request_name
531     );
532 }
533 
534 
535 bool
checkin(server_ty * sp,string_ty *,string_ty *)536 module_cvsroot::checkin(server_ty *sp, string_ty *, string_ty *)
537 {
538     groan(sp, "ci");
539     return false;
540 }
541 
542 
543 bool
add(server_ty * sp,string_ty *,string_ty *,const options &)544 module_cvsroot::add(server_ty *sp, string_ty *, string_ty *, const options &)
545 {
546     groan(sp, "add");
547     return false;
548 }
549 
550 
551 bool
remove(server_ty * sp,string_ty *,string_ty *,const options &)552 module_cvsroot::remove(server_ty *sp, string_ty *, string_ty *, const options &)
553 {
554     groan(sp, "remove");
555     return false;
556 }
557 
558 
559 // vim: set ts=8 sw=4 et :
560