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