1 //
2 // aegis - project change supervisor
3 // Copyright (C) 2004-2008, 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/stdio.h>
21 #include <common/ac/stdlib.h>
22 
23 #include <aediff/arglex3.h>
24 #include <aediff/diff.h>
25 #include <common/nstring/list.h>
26 #include <common/progname.h>
27 #include <common/trace.h>
28 #include <libaegis/arglex/project.h>
29 #include <libaegis/change/file.h>
30 #include <libaegis/change/functor.h>
31 #include <libaegis/change/identifi_sub.h>
32 #include <libaegis/file_revision.h>
33 #include <libaegis/help.h>
34 #include <libaegis/option.h>
35 #include <libaegis/os.h>
36 #include <libaegis/project.h>
37 #include <libaegis/project/identifi_sub/branch.h>
38 #include <libaegis/project/identifi_sub/plain.h>
39 #include <libaegis/sub.h>
40 #include <libaegis/user.h>
41 
42 
43 void
diff_usage()44 diff_usage()
45 {
46     const char *progname = progname_get();
47     fprintf(stderr, "Usage: %s [ <option>... ] <filename>\n", progname);
48     fprintf(stderr, "       %s --list [ <option>... ]\n", progname);
49     fprintf(stderr, "       %s --help\n", progname);
50     exit(1);
51 }
52 
53 
54 class aediff_bad_state:
55     public change_functor
56 {
57 public:
aediff_bad_state()58     aediff_bad_state() :
59         change_functor(true)
60     {
61     }
62 
63     void
operator ()(change::pointer cp)64     operator()(change::pointer cp)
65     {
66         change_fatal(cp, 0, i18n("bad patch send state"));
67     }
68 };
69 
70 static aediff_bad_state barf_adev;
71 
72 
73 static const char *
ae2diff(const char * dflt)74 ae2diff(const char *dflt)
75 {
76     const char *ep = getenv("AE2DIFF");
77     if (!ep || !*ep)
78         return dflt;
79     return ep;
80 }
81 
82 
83 void
diff()84 diff()
85 {
86     trace(("diff()\n{\n"));
87     nstring base_command(ae2diff(CONF_DIFF));
88     nstring filename;
89     project_identifier_subset_plain pid;
90     project_identifier_subset_branch first_branch(pid);
91     change_identifier_subset first(first_branch);
92     project_identifier_subset_branch second_branch(pid);
93     change_identifier_subset second(second_branch);
94     int context = 0;
95     int unified = 0;
96     bool text = false;
97     bool ignore_all_space = false;
98     bool ignore_blank_lines = false;
99     bool ignore_case = false;
100     bool ignore_space_change = false;
101     bool show_c_function = false;
102     arglex();
103     while (arglex_token != arglex_token_eoln)
104     {
105         switch (arglex_token)
106         {
107         default:
108             generic_argument(diff_usage);
109             continue;
110 
111         case arglex_token_project:
112         case arglex_token_baseline:
113         case arglex_token_branch:
114         case arglex_token_change:
115         case arglex_token_delta:
116         case arglex_token_delta_date:
117         case arglex_token_delta_from_change:
118         case arglex_token_grandparent:
119         case arglex_token_number:
120         case arglex_token_trunk:
121             if (!first.set())
122                 first.command_line_parse(diff_usage);
123             else
124                 second.command_line_parse(diff_usage);
125             continue;
126 
127         case arglex_token_string:
128             if (filename)
129                 fatal_too_many_files();
130             filename = arglex_value.alv_string;
131             break;
132 
133         case arglex_token_context:
134             if (context)
135                 duplicate_option(diff_usage);
136             if (arglex() != arglex_token_number)
137             {
138                 context = 3;
139                 continue;
140             }
141             context = arglex_value.alv_number;
142             if (context < 1)
143                 context = 1;
144             break;
145 
146         case arglex_token_unified:
147             if (unified)
148                 duplicate_option(diff_usage);
149             if (arglex() != arglex_token_number)
150             {
151                 unified = 3;
152                 continue;
153             }
154             unified = arglex_value.alv_number;
155             if (unified < 1)
156                 unified = 1;
157             break;
158 
159         case arglex_token_text:
160             if (text)
161                 duplicate_option(diff_usage);
162             text = true;
163             break;
164 
165         case arglex_token_ignore_all_space:
166             if (ignore_all_space)
167                 duplicate_option(diff_usage);
168             ignore_all_space = true;
169             break;
170 
171         case arglex_token_ignore_blank_lines:
172             if (ignore_blank_lines)
173                 duplicate_option(diff_usage);
174             ignore_blank_lines = true;
175             break;
176 
177         case arglex_token_ignore_case:
178             if (ignore_case)
179                 duplicate_option(diff_usage);
180             ignore_case = true;
181             break;
182 
183         case arglex_token_ignore_space_change:
184             if (ignore_space_change)
185                 duplicate_option(diff_usage);
186             ignore_space_change = true;
187             break;
188 
189         case arglex_token_show_c_function:
190             if (show_c_function)
191                 duplicate_option(diff_usage);
192             show_c_function = true;
193             break;
194 
195         case arglex_token_command:
196             if (arglex() != arglex_token_string)
197                 option_needs_string(arglex_token_command, diff_usage);
198             base_command = arglex_value.alv_string;
199             break;
200         }
201         arglex();
202     }
203     if (context && unified)
204     {
205         mutually_exclusive_options
206         (
207             arglex_token_context,
208             arglex_token_unified,
209             diff_usage
210         );
211     }
212     if (!filename)
213     {
214         error_intl(0, i18n("too few files named"));
215         diff_usage();
216     }
217 
218     //
219     // Default a few things if they gave zero or one change, rather than two.
220     //
221     if (!first.set() && !second.set())
222         first.set_baseline();
223 
224     //
225     // reject illegal combinations of options
226     //
227     first.command_line_check(diff_usage);
228     second.command_line_check(diff_usage);
229 
230     //
231     // Get the two revisions of the file.
232     //
233     // We get the meta-data for the file, so we have the UUID, if the
234     // file has one.  We then use the meta-data to obtain the first and
235     // second versions of the file.
236     //
237     // We look for meta-data separately to cope with a bug that make
238     // it possible for two files to have the same name but different
239     // UUIDs.
240     //
241     int number_of_errors = 0;
242     fstate_src_ty *src1 = first.get_cp()->file_find(filename, view_path_simple);
243     if (!src1)
244     {
245         src1 = first.get_cp()->file_find_fuzzy(filename, view_path_simple);
246         if (src1)
247         {
248             sub_context_ty sc;
249             sc.var_set_string("File_Name", filename);
250             sc.var_set_string("Guess", src1->file_name);
251             change_error
252             (
253                 first.get_cp(),
254                 &sc,
255                 i18n("no $filename, closest is $guess")
256             );
257         }
258 
259         sub_context_ty sc;
260         sc.var_set_string("File_Name", filename);
261         change_error(first.get_cp(), &sc, i18n("no $filename"));
262         ++number_of_errors;
263     }
264 
265     fstate_src_ty *src2 =
266         second.get_cp()->file_find(filename, view_path_simple);
267     if (!src2)
268     {
269         src2 = second.get_cp()->file_find_fuzzy(filename, view_path_simple);
270         if (src2)
271         {
272             sub_context_ty sc;
273             sc.var_set_string("File_Name", filename);
274             sc.var_set_string("Guess", src2->file_name);
275             change_error
276             (
277                 second.get_cp(),
278                 &sc,
279                 i18n("no $filename, closest is $guess")
280             );
281         }
282 
283         sub_context_ty sc;
284         sc.var_set_string("File_Name", filename);
285         change_error(second.get_cp(), &sc, i18n("no $filename"));
286         ++number_of_errors;
287     }
288 
289     if (number_of_errors > 0)
290     {
291         sub_context_ty sc;
292         sc.var_set_long("Number", number_of_errors);
293         sc.var_optional("Number");
294         project_fatal(second.get_pp(), &sc, i18n("no files diffed"));
295     }
296 
297     file_revision lhs = first.get_file_revision(src1, barf_adev);
298     trace(("lhs=%s\n", lhs.get_path().c_str()));
299 
300     file_revision rhs = second.get_file_revision(src2, barf_adev);
301     trace(("rhs=%s\n", rhs.get_path().c_str()));
302 
303     //
304     // Build the command to be executed.
305     //
306     nstring_list command_args;
307     command_args.push_back("set +e ;");
308 
309     command_args.push_back(base_command);
310     if (base_command == CONF_DIFF)
311     {
312         if (text)
313             command_args.push_back("--text");
314         if (context)
315             command_args.push_back(nstring::format("-C%d", context));
316         else if (unified)
317             command_args.push_back(nstring::format("-U%d", unified));
318 #ifdef HAVE_GNU_DIFF
319         if (context || unified)
320         {
321             nstring label = first.get_change_version_string() + "/" + filename;
322             command_args.push_back("--label=" + label.quote_shell());
323             label = second.get_change_version_string() + "/" + filename;
324             command_args.push_back("--label=" + label.quote_shell());
325         }
326 #endif
327         if (ignore_all_space)
328             command_args.push_back("--ignore-all-space");
329         if (ignore_blank_lines)
330             command_args.push_back("--ignore-blank-lines");
331         if (ignore_case)
332             command_args.push_back("--ignore-case");
333         if (ignore_space_change)
334             command_args.push_back("--ignore-space-change");
335         if (show_c_function)
336             command_args.push_back("--show-c-function");
337     }
338 
339     command_args.push_back(lhs.get_path().quote_shell());
340     command_args.push_back(rhs.get_path().quote_shell());
341     command_args.push_back("; test $? -le 1");
342 
343     nstring command = command_args.unsplit(" ");
344 
345     //
346     // Execute the command.
347     //
348     trace(("command = \"%s\"\n", command.c_str()));
349     int flags = OS_EXEC_FLAG_NO_INPUT;
350     if (!option_verbose_get())
351         flags |= OS_EXEC_FLAG_SILENT;
352     os_become_orig();
353     os_execute(command.get_ref(), flags, os_curdir());
354     os_become_undo();
355     trace(("}\n"));
356 }
357 
358 
359 // vim: set ts=8 sw=4 et :
360