1 //
2 // aegis - project change supervisor
3 // Copyright (C) 2004-2009, 2011, 2012 Peter Miller
4 // Copyright (C) 2008 Walter Franzini
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or (at
9 // your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19 
20 #include <common/ac/assert.h>
21 #include <common/ac/string.h>
22 
23 #include <common/symtab.h>
24 #include <libaegis/change/branch.h>
25 #include <libaegis/change/file.h>
26 #include <libaegis/change.h>
27 #include <libaegis/change/lock_sync.h>
28 #include <libaegis/cstate.fmtgen.h>
29 #include <libaegis/fstate.fmtgen.h>
30 #include <libaegis/input/file.h>
31 #include <libaegis/os.h>
32 #include <libaegis/output/file.h>
33 #include <libaegis/project/file.h>
34 #include <libaegis/project.h>
35 #include <libaegis/user.h>
36 
37 #include <aecvsserver/fake_version.h>
38 #include <aecvsserver/file_info.h>
39 #include <aecvsserver/module/change_bogus.h>
40 #include <aecvsserver/module/change.h>
41 #include <aecvsserver/module/project_bogu.h>
42 #include <aecvsserver/response/checked_in.h>
43 #include <aecvsserver/response/created.h>
44 #include <aecvsserver/response/new_entry.h>
45 #include <aecvsserver/response/removed.h>
46 #include <aecvsserver/response/remove_entry.h>
47 #include <aecvsserver/response/update_exist.h>
48 #include <aecvsserver/server.h>
49 
50 
~module_change()51 module_change::~module_change()
52 {
53     change_free(cp);
54     cp = 0;
55     project_free(pp);
56     pp = 0;
57 }
58 
59 
module_change(change::pointer arg)60 module_change::module_change(change::pointer arg) :
61     cp(arg),
62     pp(arg->pp),
63     up(user_ty::create())
64 {
65 }
66 
67 
68 void
modified(server_ty * sp,string_ty * file_name,file_info_ty * fip,input & contents)69 module_change::modified(server_ty *sp, string_ty *file_name, file_info_ty *fip,
70     input &contents)
71 {
72     //
73     // It is an error if the change is not in the "being developed" state.
74     //
75     if (!cp->is_being_developed())
76     {
77         server_error
78         (
79             sp,
80             "Modified: project \"%s\": change %ld: this change  must be "
81                 "in the \"being_developed\" state for this to work",
82             project_name_get(pp).c_str(),
83             cp->number
84         );
85         return;
86     }
87 
88     //
89     // It is an error if the change is actually a branch.
90     //
91     if (cp->was_a_branch())
92     {
93         server_error
94         (
95             sp,
96             "Modified: project \"%s\": change %ld: is a branch; to modify "
97                 "files you must use a change on the branch",
98             project_name_get(pp).c_str(),
99             cp->number
100         );
101         return;
102     }
103 
104     //
105     // It is an error if the change is not assigned to the current user.
106     //
107     if (nstring(cp->developer_name()) != up->name())
108     {
109         server_error
110         (
111             sp,
112             "Modified: project \"%s\": change %ld: is owned by user \"%s\", "
113                 "but it must be owned by user \"%s\" for this to work",
114             project_name_get(pp).c_str(),
115             cp->number,
116             cp->developer_name()->str_text,
117             up->name().c_str()
118         );
119     }
120 
121     //
122     // If the file doesn't exist in the change, copy it or create it
123     // as appropriate.
124     //
125     fstate_src_ty *csrc = cp->file_find(nstring(file_name), view_path_first);
126     if (!csrc)
127     {
128         string_ty       *qf;
129         const char      *verb;
130         nstring         the_command;
131         int             ok;
132 
133         nstring qp(project_name_get(pp).quote_shell());
134         qf = str_quote_shell(file_name);
135         if (!pp->file_find(file_name, view_path_extreme))
136         {
137             //
138             // FIXME: what if it's a new test?
139             // (aepatch has the same problem)
140             //
141             verb = "-new-file";
142         }
143         else
144         {
145             verb = "-copy-file";
146         }
147         the_command =
148             nstring::format
149             (
150                 "aegis %s -project=%s -change=%ld -base-relative -v %s",
151                 verb,
152                 qp.c_str(),
153                 cp->number,
154                 qf->str_text
155             );
156         str_free(qf);
157 
158         //
159         // Run the command.
160         // if there is a problem, it will also send the error to the client.
161         //
162         ok = server_execute(sp, the_command.get_ref());
163         if (!ok)
164             return;
165 
166         //
167         // Change objects can be very long lived in the aecvsserver,
168         // so make sure that we re-read the meta data soon.
169         //
170         change_lock_sync_forced(cp);
171 
172         //
173         // Now re-get the file information.
174         //
175         csrc = cp->file_find(nstring(file_name), view_path_first);
176         assert(csrc);
177     }
178     else if (csrc->action == file_action_remove)
179     {
180         server_error
181         (
182             sp,
183             "Modified: project \"%s\": change %ld: file \"%s\" is being "
184                 "removed, it makes no sense to say it's been modified",
185             project_name_get(pp).c_str(),
186             cp->number,
187             file_name->str_text
188         );
189         return;
190     }
191 
192     //
193     // Figure the path of the file to be written.
194     //
195     string_ty *dd = change_development_directory_get(cp, 0);
196     string_ty *abs_file_name = os_path_cat(dd, file_name);
197 
198     //
199     // Normalize the mode (Aegis has its own idea about file modes).
200     //
201     if (fip->mode & 0222)
202         fip->mode |= 0222;
203     if (fip->mode & 0111)
204         fip->mode |= 0111;
205     fip->mode |= 0644;
206     fip->mode &= ~cp->umask_get();
207 
208     //
209     // Copy the file contents to their destination.
210     //
211     os_become_orig();
212     output::pointer op = output_file::binary_open(abs_file_name);
213     op << contents;
214     op.reset();
215 
216     //
217     // And make sure it is in the specified mode.
218     //
219     os_chmod(abs_file_name, fip->mode);
220     os_become_undo();
221     str_free(abs_file_name);
222 }
223 
224 
225 string_ty *
calculate_canonical_name() const226 module_change::calculate_canonical_name()
227     const
228 {
229     // FIXME: memory leak
230     return
231         str_format
232         (
233             "%s.C%3.3ld",
234             project_name_get(pp).c_str(),
235             cp->number
236         );
237 }
238 
239 
240 bool
update(server_ty * sp,string_ty *,string_ty * server_side_0,const options & opt)241 module_change::update(server_ty *sp, string_ty *, string_ty *server_side_0,
242     const options &opt)
243 {
244     size_t          j;
245     static string_ty *minus_str;
246     static string_ty *zero;
247 
248     //
249     // It is an error if the change is not in the "being developed" state.
250     //
251     // FIXME: actually, it's OK if the file is between being_developed
252     // and being_integrated, but only read-only for the later ones.
253     //
254     // FIXME: what about changes in the completed state, we will need
255     // to use project_file_roll_forward instead.
256     //
257     if (!cp->is_being_developed())
258     {
259         server_error
260         (
261             sp,
262             "project \"%s\": change %ld: this change must be in the "
263                 "\"being_developed\" state for this to work",
264             project_name_get(pp).c_str(),
265             cp->number
266         );
267         return false;
268     }
269 
270     //
271     // Form a list of files by unioning the source files of the change
272     // and all the ancestor branches together.
273     //
274     // For each of these files (that the client wants to know about)
275     // send information about their contents.
276     //
277     for (j = 0; ; ++j)
278     {
279         fstate_src_ty   *src;
280         string_ty       *client_side;
281         string_ty       *server_side;
282         string_ty       *path;
283         int             need_to_unlink;
284         int             mode;
285         string_ty       *version;
286         int             is_local;
287         file_info_ty    *fip;
288 
289         src = change_file_nth(cp, j, view_path_simple);
290         if (!src)
291             break;
292         switch (src->usage)
293         {
294         case file_usage_build:
295             continue;
296 
297         case file_usage_source:
298         case file_usage_config:
299         case file_usage_test:
300         case file_usage_manual_test:
301             break;
302         }
303 
304         //
305         // Make sure the client creates the directories for us.
306         //
307         server_side = os_path_cat(name(), src->file_name);
308         if (!is_update_prefix(server_side_0, server_side, opt.d))
309         {
310             //
311             // don't create files which are not under one of the
312             // Directories specified by the client.
313             //
314             str_free(server_side);
315             continue;
316         }
317         client_side = server_directory_calc_client_side(sp, server_side);
318         server_mkdir_above(sp, client_side, server_side);
319         server_updating_verbose(sp, client_side);
320 
321         //
322         // Determine where to get the file from.
323         //
324         version = 0;
325         path = change_file_version_path(cp, src, &need_to_unlink);
326         is_local = !!cp->file_find(nstring(src->file_name), view_path_first);
327         os_become_orig();
328         input ip = input_file_open(path, need_to_unlink);
329         if (is_local && os_executable(path))
330             src->executable = true;
331         if (is_local)
332             version = fake_version(os_mtime_actual(path));
333         os_become_undo();
334         str_free(path);
335 
336         //
337         // Determine the file mode.
338         //
339         mode = 0666;
340         if (src->executable)
341             mode |= 0111;
342         mode &= ~cp->umask_get();
343 
344         //
345         // Determine the version string to send to the client.
346         // Special cases:
347         //      ""   - no user file,
348         //      "0"  - new user file,
349         //      "-"  - user file to be removed
350         //
351         if (!version)
352         {
353             switch (src->action)
354             {
355             case file_action_remove:
356                 //
357                 // What do do depends on whether the file exists on the
358                 // client or not.
359                 //
360                 // the client has never heard of it
361                 //     do nothing
362                 //
363                 // The client has no Entry for it, but said Questionable
364                 //     do nothing
365                 //
366                 // the client Entry has a removed version ("-")
367                 //     do nothing.
368                 //
369                 // the client Entry has a new file version ("0")
370                 //     do nothing.
371                 //
372                 // the client says it exists
373                 //     if Is-modified
374                 //         send the new file ("0") Entry
375                 //     otherwise
376                 //         send the Removed response, which will remove
377                 //         the entry and the file from the client side.
378                 //
379                 fip = server_file_info_find(sp, server_side, 0);
380                 if (!fip || !fip->version)
381                     goto do_nothing;
382                 if (!minus_str)
383                     minus_str = str_from_c("-");
384                 if (!zero)
385                     zero = str_from_c("0");
386                 if
387                 (
388                     str_equal(fip->version, minus_str)
389                 ||
390                     str_equal(fip->version, zero)
391                 )
392                     goto do_nothing;
393                 if (fip->modified > 0)
394                 {
395                     version = str_copy(zero);
396                     break;
397                 }
398                 server_response_queue
399                 (
400                     sp,
401                     new response_removed(client_side, server_side)
402                 );
403                 goto do_nothing;
404 
405             case file_action_transparent:
406                 version = str_from_c("0");
407                 break;
408 
409             case file_action_create:
410             case file_action_modify:
411             case file_action_insulate:
412                 if (src->edit && src->edit->revision)
413                     version = str_copy(src->edit->revision);
414                 else
415                     version = str_from_c("0");
416                 break;
417             }
418             if (!version)
419                 version = str_from_c("");
420         }
421 
422         fip = server_file_info_find(sp, server_side, 0);
423         if (!fip)
424         {
425             //
426             // Queue the response to be sent.
427             //
428             server_response_queue
429             (
430                 sp,
431                 new response_created
432                 (
433                     client_side,
434                     server_side,
435                     ip,
436                     mode,
437                     version
438                 )
439             );
440         }
441         else if (opt.C)
442         {
443             //
444             // We have been told to over-write what they have on the
445             // client side.
446             //
447             if (fip->modified > 0)
448             {
449                 server_response_queue
450                 (
451                     sp,
452                     new response_update_existing
453                     (
454                         client_side,
455                         server_side,
456                         ip,
457                         mode,
458                         version
459                     )
460                 );
461             }
462         }
463         else if (str_equal(fip->version, version))
464         {
465             //
466             // What they copied the first time is still the current
467             // version on the server side.
468             //
469             if (fip->modified > 0)
470             {
471                 string_ty       *sub;
472 
473                 //
474                 // Remind them they need to commit it.
475                 //
476                 sub = os_entryname(client_side);
477                 server_m(sp, "M %s\n", sub->str_text);
478                 str_free(sub);
479             }
480         }
481         else
482         {
483             //
484             // What they copied the first time is no longer the
485             // current version on the server side.
486             //
487             if (fip->modified)
488             {
489                 //
490                 // The server side change and the client side changed.
491                 // The Modified request (received earlier) has copied
492                 // the file into the change, and the over-written it.
493                 //
494                 // We have to send the file back to them,
495                 // AFTER running a merge command.
496                 //
497                 // FIXME: run the merge command
498                 //
499                 server_response_queue
500                 (
501                     sp,
502                     new response_update_existing
503                     (
504                         client_side,
505                         server_side,
506                         ip,
507                         mode,
508                         version
509                     )
510                 );
511             }
512             else
513             {
514                 //
515                 // The client side is out-of-date, resend the file and
516                 // over-write the client-side contents.
517                 //
518                 server_response_queue
519                 (
520                     sp,
521                     new response_update_existing
522                     (
523                         client_side,
524                         server_side,
525                         ip,
526                         mode,
527                         version
528                     )
529                 );
530             }
531         }
532     do_nothing:
533         str_free(client_side);
534         str_free(server_side);
535         str_free(version);
536 
537         os_become_orig();
538         ip.close();
539         os_become_undo();
540     }
541     return true;
542 }
543 
544 
545 static bool
file_being_deleted(server_ty * sp,string_ty * server_side)546 file_being_deleted(server_ty *sp, string_ty *server_side)
547 {
548     file_info_ty    *fip;
549     static string_ty *minus_str;
550 
551     fip = server_file_info_find(sp, server_side, 0);
552     if (!fip)
553         return false;
554     if (!fip->version)
555         return false;
556     if (!minus_str)
557         minus_str = str_from_c("-");
558     return str_equal(fip->version, minus_str);
559 }
560 
561 
562 bool
checkin(server_ty * sp,string_ty * client_side,string_ty * server_side)563 module_change::checkin(server_ty *sp, string_ty *client_side,
564     string_ty *server_side)
565 {
566     fstate_src_ty   *src;
567     int             mode;
568     string_ty       *version;
569     string_ty       *filename;
570     const char      *strp;
571 
572     //
573     // It is an error if the change is not in the "being developed" state.
574     //
575     if (!cp->is_being_developed())
576     {
577         server_error
578         (
579             sp,
580             "ci: project \"%s\": change %ld: this change must be "
581                 "in the \"being_developed\" state for this to work",
582             project_name_get(pp).c_str(),
583             cp->number
584         );
585         return false;
586     }
587 
588     //
589     // Extract the baseline-relative name of the file.
590     //
591     // The server-side string sent by the client will be
592     // ROOT_PATH/project.Cnnn/filename so skip three slashes (ROOT_PATH
593     // starts with a slash) and use the rest.  Except that the Directory
594     // request code strips off the ROOT_PATH/ part, so skip one slash
595     // instead.
596     //
597     strp = strchr(server_side->str_text, '/');
598     assert(strp);
599     filename = str_from_c(strp ? strp + 1 : ".");
600 
601     src = cp->file_find(nstring(filename), view_path_first);
602     if (!src)
603     {
604         if (file_being_deleted(sp, server_side))
605         {
606             string_ty       *qf;
607             string_ty       *the_command;
608             int             ok;
609 
610             nstring qp(project_name_get(pp).quote_shell());
611             qf = str_quote_shell(filename);
612             the_command =
613                 str_format
614                 (
615           "aegis --remove-file --project=%s --change=%ld --base-relative -v %s",
616                     qp.c_str(),
617                     cp->number,
618                     qf->str_text
619                 );
620             str_free(qf);
621 
622             //
623             // Run the command.  If there is a problem, it will also
624             // send the error to the client.
625             //
626             ok = server_execute(sp, the_command);
627             str_free(the_command);
628             if (!ok)
629                 return false;
630 
631             //
632             // Change objects can be very long lived in the aecvsserver,
633             // so make sure that we re-read the meta data soon.
634             //
635             change_lock_sync_forced(cp);
636 
637             //
638             // Let the client know the file is well and truly gone.
639             // (We dont use the Removed response because the file is
640             // already deleted from thwe client.)
641             //
642             server_response_queue
643             (
644                 sp,
645                 new response_remove_entry(client_side, server_side)
646             );
647 
648             //
649             // Report success.
650             //
651             return true;
652         }
653 
654         server_error
655         (
656             sp,
657             "ci: project \"%s\": change %ld: file \"%s\" unknown",
658             project_name_get(pp).c_str(),
659             cp->number,
660             filename->str_text
661         );
662         return false;
663     }
664 
665     //
666     // For changes which are being developeed, like this one, we
667     // have to look at the file itself for the executable bit.
668     //
669     if (src->action != file_action_remove)
670     {
671         string_ty       *path;
672 
673         path = cp->file_path(filename);
674         os_become_orig();
675         if (os_executable(path))
676             src->executable = true;
677         os_become_undo();
678         str_free(path);
679     }
680 
681     //
682     // Determine the file mode.
683     //
684     mode = 0666;
685     if (src->executable)
686         mode |= 0111;
687     mode &= ~cp->umask_get();
688 
689     //
690     // Determine the version string to send to the client.
691     // Special cases:
692     //  ""   - no user file,
693     //  "0"  - new user file,
694     //  "-"  - user file to be removed
695     //
696     version = 0;
697     switch (src->action)
698     {
699     case file_action_remove:
700         version = str_from_c("-");
701         break;
702 
703     case file_action_transparent:
704         version = str_from_c("0");
705         break;
706 
707     case file_action_create:
708     case file_action_modify:
709     case file_action_insulate:
710         if (src->edit && src->edit->revision)
711             version = str_copy(src->edit->revision);
712         else
713             version = str_from_c("0");
714         break;
715     }
716     if (!version)
717         version = str_from_c("");
718 
719     //
720     // Queue the response to be sent.
721     //
722     server_response_queue
723     (
724         sp,
725         new response_checked_in(client_side, server_side, mode, version)
726     );
727     str_free(version);
728 
729     return true;
730 }
731 
732 
733 bool
add(server_ty * sp,string_ty * client_side,string_ty * server_side,const options &)734 module_change::add(server_ty *sp, string_ty *client_side,
735     string_ty *server_side, const options &)
736 {
737     fstate_src_ty   *src;
738     int             mode;
739     string_ty       *version;
740     string_ty       *filename;
741     const char      *strp;
742 
743     //
744     // It is an error if the change is not in the "being developed" state.
745     //
746     if (!cp->is_being_developed())
747     {
748         server_error
749         (
750             sp,
751             "add: project \"%s\": change %ld: this change must be "
752                 "in the \"being_developed\" state for this to work",
753             project_name_get(pp).c_str(),
754             cp->number
755         );
756         return false;
757     }
758 
759     //
760     // Extract the baseline-relative name of the file.
761     //
762     // The server-side string sent by the client will be
763     // ROOT_PATH/project.Cnnn/filename so skip three slashes and use
764     // the rest.  Except that the Directory request code strips off
765     // the ROOT_PATH/ part, so skip one slash instead.
766     //
767     strp = strchr(server_side->str_text, '/');
768     assert(strp);
769     filename = str_from_c(strp ? strp + 1 : ".");
770 
771     //
772     // The client sends an "Is-modified" request, so the file isn't
773     // necesarily created yet.
774     //
775     src = cp->file_find(nstring(filename), view_path_first);
776     if (src)
777     {
778         //
779         // The CVS documentation says that a "cvs add" of a removed file
780         // will re-instate it.
781         //
782         if (src->action == file_action_remove)
783         {
784             string_ty       *qf;
785             string_ty       *the_command;
786             int             ok;
787 
788             nstring qp(project_name_get(pp).quote_shell());
789             qf = str_quote_shell(filename);
790             the_command =
791                 str_format
792                 (
793                     "aegis -rmu -project=%s -change=%ld -base-relative -v %s",
794                     qp.c_str(),
795                     cp->number,
796                     qf->str_text
797                 );
798 
799             //
800             // Run the command.
801             // if there is a problem, it will also send the error to the client.
802             //
803             ok = server_execute(sp, the_command);
804             str_free(the_command);
805             if (!ok)
806             {
807                 str_free(qf);
808                 return false;
809             }
810 
811             //
812             // Change objects can be very long lived in the aecvsserver,
813             // so make sure that we re-read the meta data soon.
814             //
815             change_lock_sync_forced(cp);
816 
817             //
818             // Now re-get the file information.
819             //
820             src = pp->file_find(filename, view_path_extreme);
821             if (src)
822             {
823                 the_command =
824                     str_format
825                     (
826                 "aegis -copy-file -project=%s -change=%ld -base-relative -v %s",
827                         qp.c_str(),
828                         cp->number,
829                         qf->str_text
830                     );
831 
832                 //
833                 // Run the command.  if there is a problem, it will also
834                 // send the error to the client.
835                 //
836                 ok = server_execute(sp, the_command);
837                 str_free(the_command);
838                 if (!ok)
839                 {
840                     str_free(qf);
841                     return false;
842                 }
843 
844                 //
845                 // Change objects can be very long lived in the aecvsserver,
846                 // so make sure that we re-read the meta data soon.
847                 //
848                 change_lock_sync_forced(cp);
849 
850                 //
851                 // Now re-get the file information.
852                 //
853                 src = cp->file_find(nstring(filename), view_path_first);
854                 assert(src);
855             }
856             str_free(qf);
857         }
858     }
859 
860     //
861     // For changes which are being developeed, like this one, we
862     // have to look at the file itself for the executable bit.
863     //
864     if (src && src->action != file_action_remove)
865     {
866         string_ty       *path;
867 
868         path = cp->file_path(filename);
869         os_become_orig();
870         if (os_executable(path))
871             src->executable = true;
872         os_become_undo();
873         str_free(path);
874     }
875 
876     //
877     // Determine the file mode.
878     //
879     mode = 0666;
880     if (src && src->executable)
881         mode |= 0111;
882     mode &= ~cp->umask_get();
883 
884     if (!src)
885     {
886         version = str_from_c("0");
887         server_e
888         (
889             sp,
890             "scheduling file `%s' for addition",
891             client_side->str_text
892         );
893         server_response_queue
894         (
895             sp,
896             new response_new_entry(client_side, server_side, mode, version)
897         );
898         str_free(version);
899 
900         return true;
901     }
902 
903     //
904     // Determine the version string to send to the client.
905     // Special cases:
906     //  ""   - no user file,
907     //  "0"  - new user file,
908     //  "-"  - user file to be removed
909     //
910     version = 0;
911     switch (src->action)
912     {
913     case file_action_remove:
914         version = str_from_c("-");
915         break;
916 
917     case file_action_transparent:
918     case file_action_create:
919         version = str_from_c("0");
920         break;
921 
922     case file_action_modify:
923     case file_action_insulate:
924         if (src->edit && src->edit->revision)
925             version = str_copy(src->edit->revision);
926         else
927             version = str_from_c("0");
928         break;
929     }
930     if (!version)
931         version = str_from_c("");
932 
933     //
934     // Queue the response to be sent.
935     //
936     server_e
937     (
938         sp,
939         "scheduling file `%s' for addition",
940         src->file_name->str_text
941     );
942     server_response_queue
943     (
944         sp,
945         new response_new_entry(client_side, server_side, mode, version)
946     );
947     str_free(version);
948 
949     return true;
950 }
951 
952 
953 bool
remove(server_ty * sp,string_ty * client_side,string_ty * server_side,const options &)954 module_change::remove(server_ty *sp, string_ty *client_side,
955     string_ty *server_side, const options &)
956 {
957     fstate_src_ty   *src;
958     int             mode;
959     string_ty       *version;
960     string_ty       *filename;
961     const char      *strp;
962 
963     //
964     // It is an error if the change is not in the "being developed" state.
965     //
966     if (!cp->is_being_developed())
967     {
968         server_error
969         (
970             sp,
971             "remove: project \"%s\": change %ld: this change must be "
972                 "in the \"being_developed\" state for this to work",
973             project_name_get(pp).c_str(),
974             cp->number
975         );
976         return false;
977     }
978 
979     //
980     // Extract the baseline-relative name of the file.
981     //
982     // The server-side string sent by the client will be
983     // ROOT_PATH/project.Cnnn/filename so skip three slashes and use
984     // the rest.  Except that the Directory request code strips off
985     // the ROOT_PATH/ part, so skip one slash instead.
986     //
987     strp = strchr(server_side->str_text, '/');
988     assert(strp);
989     filename = str_from_c(strp ? strp + 1 : ".");
990 
991     //
992     // If the file already exists in the change,
993     // we may have to undo that.
994     //
995     src = cp->file_find(nstring(filename), view_path_first);
996     if (src)
997     {
998         string_ty       *qf;
999         string_ty       *the_command;
1000         int             ok;
1001         const char      *verb;
1002 
1003         verb = "--copy-file-undo";
1004         switch (src->action)
1005         {
1006         case file_action_create:
1007             verb = "--new-file-undo";
1008             switch (src->usage)
1009             {
1010             case file_usage_source:
1011             case file_usage_config:
1012             case file_usage_build:
1013                 break;
1014 
1015             case file_usage_test:
1016             case file_usage_manual_test:
1017                 verb = "--new-test-undo";
1018                 break;
1019             }
1020             break;
1021 
1022         case  file_action_modify:
1023         case  file_action_insulate:
1024             break;
1025 
1026         case file_action_transparent:
1027             verb = "--make-transparent-undo";
1028             break;
1029 
1030         case file_action_remove:
1031             goto already_being_removed;
1032         }
1033 
1034         nstring qp = project_name_get(pp).quote_shell();
1035         qf = str_quote_shell(filename);
1036         the_command =
1037             str_format
1038             (
1039                 "aegis %s -project=%s -change=%ld -base-relative -v %s",
1040                 verb,
1041                 qp.c_str(),
1042                 cp->number,
1043                 qf->str_text
1044             );
1045         str_free(qf);
1046 
1047         //
1048         // Run the command.
1049         // if there is a problem, it will also send the error to the client.
1050         //
1051         ok = server_execute(sp, the_command);
1052         str_free(the_command);
1053         if (!ok)
1054             return false;
1055 
1056         //
1057         // Change objects can be very long lived in the aecvsserver,
1058         // so make sure that we re-read the meta data soon.
1059         //
1060         change_lock_sync_forced(cp);
1061 
1062         switch (src->action)
1063         {
1064         case file_action_create:
1065             server_response_queue
1066             (
1067                 sp,
1068                 new response_remove_entry(client_side, server_side)
1069             );
1070             return true;
1071 
1072         case file_action_remove:
1073         case file_action_modify:
1074         case file_action_insulate:
1075         case file_action_transparent:
1076             break;
1077         }
1078         src = 0;
1079     }
1080 
1081     src = pp->file_find(filename, view_path_extreme);
1082     if (!src)
1083     {
1084         server_error
1085         (
1086             sp,
1087             "project \"%s\": change %ld: file \"%s\" cannot be removed "
1088                 "because it does not exist in the project",
1089             project_name_get(pp).c_str(),
1090             cp->number,
1091             filename->str_text
1092         );
1093         return false;
1094     }
1095 
1096     //
1097     // Determine the version string to send to the client.
1098     // Special cases:
1099     //  ""   - no user file,
1100     //  "0"  - new user file,
1101     //  "-"  - user file to be removed
1102     //
1103     already_being_removed:
1104     version = str_from_c("-");;
1105     mode = 0644;
1106 
1107     //
1108     // Queue the response to be sent.
1109     //
1110     server_e
1111     (
1112         sp,
1113         "scheduling file `%s' for removal",
1114         src->file_name->str_text
1115     );
1116     server_response_queue
1117     (
1118         sp,
1119         new response_new_entry(client_side, server_side, mode, version)
1120     );
1121     str_free(version);
1122 
1123     return true;
1124 }
1125 
1126 
1127 module_ty *
module_change_new(string_ty * project_name,long change_number)1128 module_change_new(string_ty *project_name, long change_number)
1129 {
1130     //
1131     // Make sure the project makes sense.
1132     //
1133     project *pp = project_alloc(project_name);
1134     if (!pp->bind_existing_errok())
1135     {
1136         project_free(pp);
1137         return new module_project_bogus(project_name);
1138     }
1139 
1140     //
1141     // Make sure the change makes sense.
1142     //
1143     change::pointer cp = change_alloc(pp, change_number);
1144     if (!change_bind_existing_errok(cp))
1145     {
1146         change_free(cp);
1147         project_free(pp);
1148         return new module_change_bogus(project_name, change_number);
1149     }
1150 
1151     //
1152     // OK, looks like we have a viable change.
1153     //
1154     return new module_change(cp);
1155 }
1156 
1157 
1158 // vim: set ts=8 sw=4 et :
1159