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