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