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