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