1 //
2 // aegis - project change supervisor
3 // Copyright (C) 1991-1999, 2001-2009, 2011, 2012 Peter Miller
4 // Copyright (C) 2008, 2009 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/stdio.h>
22 #include <common/ac/stdlib.h>
23 
24 #include <common/progname.h>
25 #include <common/quit.h>
26 #include <common/sizeof.h>
27 #include <common/str_list.h>
28 #include <common/trace.h>
29 #include <libaegis/ael/change/files.h>
30 #include <libaegis/arglex/change.h>
31 #include <libaegis/arglex/project.h>
32 #include <libaegis/arglex2.h>
33 #include <libaegis/change/branch.h>
34 #include <libaegis/change/file.h>
35 #include <libaegis/change/identifier.h>
36 #include <libaegis/commit.h>
37 #include <libaegis/help.h>
38 #include <libaegis/lock.h>
39 #include <libaegis/log.h>
40 #include <libaegis/os.h>
41 #include <libaegis/project.h>
42 #include <libaegis/project/file.h>
43 #include <libaegis/search_path/base_get.h>
44 #include <libaegis/sub.h>
45 #include <libaegis/user.h>
46 
47 #include <aegis/aermu.h>
48 
49 
50 //
51 // NAME
52 //      remove_file_undo_usage
53 //
54 // SYNOPSIS
55 //      void remove_file_undo_usage(void);
56 //
57 // DESCRIPTION
58 //      The remove_file_undo_usage function is used to
59 //      tell the user how to use the 'aegis -ReMove_file_Undo' command.
60 //
61 
62 static void
remove_file_undo_usage(void)63 remove_file_undo_usage(void)
64 {
65     const char      *progname;
66 
67     progname = progname_get();
68     fprintf
69     (
70         stderr,
71         "usage: %s -ReMove_file_Undo <filename>... [ <option>... ]\n",
72         progname
73     );
74     fprintf
75     (
76         stderr,
77         "       %s -ReMove_file_Undo -List [ <option>... ]\n",
78         progname
79     );
80     fprintf(stderr, "       %s -ReMove_file_Undo -Help\n", progname);
81     quit(1);
82 }
83 
84 
85 //
86 // NAME
87 //      remove_file_undo_help
88 //
89 // SYNOPSIS
90 //      void remove_file_undo_help(void);
91 //
92 // DESCRIPTION
93 //      The remove_file_undo_help function is used to
94 //      describe the 'aegis -ReMove_file_undo' command to the user.
95 //
96 
97 static void
remove_file_undo_help(void)98 remove_file_undo_help(void)
99 {
100     help("aermu", remove_file_undo_usage);
101 }
102 
103 
104 //
105 // NAME
106 //      remove_file_undo_list
107 //
108 // SYNOPSIS
109 //      void remove_file_undo_list(void);
110 //
111 // DESCRIPTION
112 //      The remove_file_undo_list function is used to
113 //      list the file the user may wish to remove from the change
114 //      as a deletion.  All relevant change files are listed.
115 //
116 
117 static void
remove_file_undo_list(void)118 remove_file_undo_list(void)
119 {
120     trace(("remove_file_undo_list()\n{\n"));
121     arglex();
122     change_identifier cid;
123     cid.command_line_parse_rest(remove_file_undo_usage);
124     list_change_files(cid, 0);
125     trace(("}\n"));
126 }
127 
128 
129 static int
candidate(fstate_src_ty * src)130 candidate(fstate_src_ty *src)
131 {
132     if (src->move)
133         return 0;
134     switch (src->action)
135     {
136     case file_action_remove:
137         return 1;
138 
139     case file_action_create:
140     case file_action_modify:
141     case file_action_insulate:
142     case file_action_transparent:
143         break;
144     }
145     return 0;
146 }
147 
148 
149 //
150 // NAME
151 //      remove_undo_main
152 //
153 // SYNOPSIS
154 //      void remove_undo_main(void);
155 //
156 // DESCRIPTION
157 //      The remove_undo_main function is used to
158 //      remove a file from a change as a deletion.
159 //
160 //      The names of the relevant files are gleaned from the command line.
161 //
162 
163 static void
remove_file_undo_main(void)164 remove_file_undo_main(void)
165 {
166     string_ty       *s1;
167     string_ty       *s2;
168     size_t          j;
169     size_t          k;
170     project      *pp;
171     change::pointer cp;
172     user_ty::pointer up;
173     string_ty       *dd;
174     int             number_of_errors;
175     string_list_ty  search_path;
176 
177     trace(("remove_file_undo_main()\n{\n"));
178     arglex();
179     string_ty *project_name = 0;
180     long change_number = 0;
181     string_list_ty wl;
182     log_style_ty log_style = log_style_append_default;
183     while (arglex_token != arglex_token_eoln)
184     {
185         switch (arglex_token)
186         {
187         default:
188             generic_argument(remove_file_undo_usage);
189             continue;
190 
191         case arglex_token_file:
192         case arglex_token_directory:
193             if (arglex() != arglex_token_string)
194                 remove_file_undo_usage();
195             // fall through...
196 
197         case arglex_token_string:
198             s2 = str_from_c(arglex_value.alv_string);
199             wl.push_back(s2);
200             str_free(s2);
201             break;
202 
203         case arglex_token_change:
204             arglex();
205             // fall through...
206 
207         case arglex_token_number:
208             arglex_parse_change
209             (
210                 &project_name,
211                 &change_number,
212                 remove_file_undo_usage
213             );
214             continue;
215 
216         case arglex_token_project:
217             arglex();
218             arglex_parse_project(&project_name, remove_file_undo_usage);
219             continue;
220 
221         case arglex_token_nolog:
222             if (log_style == log_style_none)
223                 duplicate_option(remove_file_undo_usage);
224             log_style = log_style_none;
225             break;
226 
227         case arglex_token_wait:
228         case arglex_token_wait_not:
229             user_ty::lock_wait_argument(remove_file_undo_usage);
230             break;
231 
232         case arglex_token_base_relative:
233         case arglex_token_current_relative:
234             user_ty::relative_filename_preference_argument
235             (
236                 remove_file_undo_usage
237             );
238             break;
239 
240         case arglex_token_symbolic_links:
241         case arglex_token_symbolic_links_not:
242             user_ty::symlink_pref_argument(remove_file_undo_usage);
243             break;
244         }
245         arglex();
246     }
247     if (!wl.nstrings)
248     {
249         error_intl(0, i18n("no file names"));
250         remove_file_undo_usage();
251     }
252 
253     //
254     // locate project data
255     //
256     if (!project_name)
257     {
258         nstring n = user_ty::create()->default_project();
259         project_name = str_copy(n.get_ref());
260     }
261     pp = project_alloc(project_name);
262     str_free(project_name);
263     pp->bind_existing();
264 
265     //
266     // locate user data
267     //
268     up = user_ty::create();
269 
270     //
271     // locate change data
272     //
273     if (!change_number)
274         change_number = up->default_change(pp);
275     cp = change_alloc(pp, change_number);
276     change_bind_existing(cp);
277 
278     //
279     // take the locks and read the change state
280     //
281     change_cstate_lock_prepare(cp);
282     lock_take();
283 
284     log_open(change_logfile_get(cp), up, log_style);
285 
286     //
287     // It is an error if the change is not in the in_development state.
288     // It is an error if the change is not assigned to the current user.
289     //
290     if (!cp->is_being_developed())
291         change_fatal(cp, 0, i18n("bad rmu state"));
292     if (cp->is_a_branch())
293         change_fatal(cp, 0, i18n("bad cp undo branch"));
294     if (nstring(cp->developer_name()) != up->name())
295         change_fatal(cp, 0, i18n("not developer"));
296 
297     //
298     // Search path for resolving filenames.
299     //
300     cp->search_path_get(&search_path, true);
301 
302     //
303     // Find the base for relative filenames.
304     //
305     nstring base(search_path_base_get(search_path, up));
306 
307     //
308     // resolve the path of each file
309     // 1.   the absolute path of the file name is obtained
310     // 2.   if the file is inside the development directory, ok
311     // 3.   if the file is inside the baseline, ok
312     // 4.   if neither, error
313     //
314     string_list_ty wl2;
315     number_of_errors = 0;
316     for (j = 0; j < wl.nstrings; ++j)
317     {
318         string_list_ty  wl_in;
319 
320         s1 = wl.string[j];
321         if (s1->str_text[0] == '/')
322             s2 = str_copy(s1);
323         else
324             s2 = os_path_join(base.get_ref(), s1);
325         up->become_begin();
326         s1 = os_pathname(s2, 1);
327         up->become_end();
328         str_free(s2);
329         s2 = 0;
330         for (k = 0; k < search_path.nstrings; ++k)
331         {
332             s2 = os_below_dir(search_path.string[k], s1);
333             if (s2)
334                 break;
335         }
336         str_free(s1);
337         if (!s2)
338         {
339             sub_context_ty  *scp;
340 
341             scp = sub_context_new();
342             sub_var_set_string(scp, "File_Name", wl.string[j]);
343             change_error(cp, scp, i18n("$filename unrelated"));
344             sub_context_delete(scp);
345             ++number_of_errors;
346             continue;
347         }
348         change_file_directory_query(cp, s2, &wl_in, 0);
349         if (wl_in.nstrings)
350         {
351             int             used;
352 
353             //
354             // If the user named a directory, add all of the
355             // source files in that directory, provided they
356             // are not already in the change.
357             //
358             used = 0;
359             for (k = 0; k < wl_in.nstrings; ++k)
360             {
361                 fstate_src_ty   *src_data;
362                 string_ty       *s3;
363 
364                 s3 = wl_in.string[k];
365                 src_data = cp->file_find(nstring(s3), view_path_first);
366                 assert(src_data);
367                 if (src_data && candidate(src_data))
368                 {
369                     if (wl2.member(s3))
370                     {
371                         sub_context_ty  *scp;
372 
373                         scp = sub_context_new();
374                         sub_var_set_string(scp, "File_Name", s3);
375                         change_error(cp, scp, i18n("too many $filename"));
376                         sub_context_delete(scp);
377                         ++number_of_errors;
378                     }
379                     else
380                         wl2.push_back(s3);
381                     ++used;
382                 }
383             }
384             if (!used)
385             {
386                 sub_context_ty  *scp;
387 
388                 scp = sub_context_new();
389                 if (s2->str_length)
390                     sub_var_set_string(scp, "File_Name", s2);
391                 else
392                     sub_var_set_charstar(scp, "File_Name", ".");
393                 sub_var_set_long(scp, "Number", (long)wl_in.nstrings);
394                 sub_var_optional(scp, "Number");
395                 change_error
396                 (
397                     cp,
398                     scp,
399                     i18n("directory $filename contains no relevant files")
400                 );
401                 sub_context_delete(scp);
402                 ++number_of_errors;
403             }
404         }
405         else
406         {
407             if (wl2.member(s2))
408             {
409                 sub_context_ty  *scp;
410 
411                 scp = sub_context_new();
412                 sub_var_set_string(scp, "File_Name", s2);
413                 change_error(cp, scp, i18n("too many $filename"));
414                 sub_context_delete(scp);
415                 ++number_of_errors;
416             }
417             else
418                 wl2.push_back(s2);
419         }
420         str_free(s2);
421     }
422     wl = wl2;
423 
424     //
425     // ensure that each file is part of the change
426     //
427     for (j = 0; j < wl.nstrings; ++j)
428     {
429         fstate_src_ty   *c_src_data;
430 
431         s1 = wl.string[j];
432         c_src_data = cp->file_find(nstring(s1), view_path_first);
433         if (!c_src_data)
434         {
435             c_src_data = cp->file_find_fuzzy(nstring(s1), view_path_first);
436             if (c_src_data)
437             {
438                 sub_context_ty  *scp;
439 
440                 scp = sub_context_new();
441                 sub_var_set_string(scp, "File_Name", s1);
442                 sub_var_set_string(scp, "Guess", c_src_data->file_name);
443                 change_error(cp, scp, i18n("no $filename, closest is $guess"));
444                 sub_context_delete(scp);
445             }
446             else
447             {
448                 sub_context_ty  *scp;
449 
450                 scp = sub_context_new();
451                 sub_var_set_string(scp, "File_Name", s1);
452                 change_error(cp, scp, i18n("no $filename"));
453                 sub_context_delete(scp);
454             }
455             ++number_of_errors;
456             continue;
457         }
458         if (!candidate(c_src_data))
459         {
460             sub_context_ty  *scp;
461 
462             scp = sub_context_new();
463             sub_var_set_string(scp, "File_Name", s1);
464             change_error(cp, scp, i18n("bad rm undo $filename"));
465             sub_context_delete(scp);
466             ++number_of_errors;
467             continue;
468         }
469         change_file_remove(cp, s1);
470     }
471     if (number_of_errors)
472     {
473         sub_context_ty  *scp;
474 
475         scp = sub_context_new();
476         sub_var_set_long(scp, "Number", number_of_errors);
477         sub_var_optional(scp, "Number");
478         change_fatal(cp, scp, i18n("remove file undo fail"));
479         // NOTREACHED
480         sub_context_delete(scp);
481     }
482 
483     //
484     // Remove the difference files,
485     // and the dummy files,
486     // if they exist.
487     //
488     dd = change_development_directory_get(cp, 0);
489     up->become_begin();
490     for (j = 0; j < wl.nstrings; ++j)
491     {
492         s1 = wl.string[j];
493         s2 = os_path_join(dd, s1);
494 
495         //
496         // This is not as robust in the face of errors
497         // as using commit.  Its merit is its simplicity.
498         //
499         // It plays nice with the change_maintain_symlinks_to_baseline
500         // call below.
501         //
502         // Also, the rename-and-delete shenanigans take
503         // a long time over NFS, and users expect this
504         // to be fast.
505         //
506         if (os_exists(s2))
507             os_unlink(s2);
508         str_free(s2);
509 
510         //
511         // Always unlink the difference file.
512         //
513         s2 = str_format("%s/%s,D", dd->str_text, s1->str_text);
514         if (os_exists(s2))
515             commit_unlink_errok(s2);
516         str_free(s2);
517 
518         //
519         // Always unlink the merge backup file.
520         //
521         s2 = str_format("%s/%s,B", dd->str_text, s1->str_text);
522         if (os_exists(s2))
523             commit_unlink_errok(s2);
524         str_free(s2);
525     }
526     up->become_end();
527 
528     //
529     // the number of files changed, or the version did,
530     // so stomp on the validation fields.
531     //
532     change_build_times_clear(cp);
533 
534     //
535     // If the file manifest of the change is altered (e.g. by aenf, aenfu,
536     // aecp, aecpu, etc), or the contents of any file is changed, the
537     // UUID is cleared.  This is because it is no longer the same change
538     // as was received by aedist or aepatch, and the UUID is invalidated.
539     //
540     change_uuid_clear(cp);
541 
542     //
543     // Maintain the symlinks (etc) to the baseline.
544     //
545     change_maintain_symlinks_to_baseline(cp, up);
546 
547     // remember we are about to
548     bool recent_integration = cp->run_project_file_command_needed();
549     if (recent_integration)
550         cp->run_project_file_command_done();
551 
552     //
553     // write the data and release the lock
554     //
555     cp->cstate_write();
556     commit();
557     lock_release();
558 
559     //
560     // run the change file command
561     // and the project file command if necessary
562     //
563     cp->run_remove_file_undo_command(&wl, up);
564     if (recent_integration)
565         cp->run_project_file_command(up);
566 
567     //
568     // verbose success message
569     //
570     for (j = 0; j < wl.nstrings; ++j)
571     {
572         sub_context_ty  *scp;
573 
574         scp = sub_context_new();
575         sub_var_set_string(scp, "File_Name", wl.string[j]);
576         change_verbose(cp, scp, i18n("remove file undo $filename complete"));
577         sub_context_delete(scp);
578     }
579     change_free(cp);
580     project_free(pp);
581     trace(("}\n"));
582 }
583 
584 
585 //
586 // NAME
587 //      remove_file_undo
588 //
589 // SYNOPSIS
590 //      void remove_file_undo(void);
591 //
592 // DESCRIPTION
593 //      The remove_file_undo function is used to
594 //      dispatch the 'aegis -ReMove_file_Undo' command to the relevant
595 //      function to do it's work.
596 //
597 
598 void
remove_file_undo(void)599 remove_file_undo(void)
600 {
601     static arglex_dispatch_ty dispatch[] =
602     {
603         { arglex_token_help, remove_file_undo_help, 0 },
604         { arglex_token_list, remove_file_undo_list, 0 },
605     };
606 
607     trace(("remove_file_undo()\n{\n"));
608     arglex_dispatch(dispatch, SIZEOF(dispatch), remove_file_undo_main);
609     trace(("}\n"));
610 }
611 
612 
613 // vim: set ts=8 sw=4 et :
614