Followup to r29625: fix getopt tests.
[svn.git] / subversion / svn / diff-cmd.c
blob81473f23fd64fe850ef79a17f9d260b8cb1575ca
1 /*
2 * diff-cmd.c -- Display context diff of a file
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
23 /*** Includes. ***/
25 #include "svn_pools.h"
26 #include "svn_client.h"
27 #include "svn_string.h"
28 #include "svn_path.h"
29 #include "svn_error_codes.h"
30 #include "svn_error.h"
31 #include "svn_types.h"
32 #include "svn_cmdline.h"
33 #include "svn_xml.h"
34 #include "cl.h"
36 #include "svn_private_config.h"
39 /*** Code. ***/
41 /* Convert KIND into a single character for display to the user. */
42 static char
43 kind_to_char(svn_client_diff_summarize_kind_t kind)
45 switch (kind)
47 case svn_client_diff_summarize_kind_modified:
48 return 'M';
50 case svn_client_diff_summarize_kind_added:
51 return 'A';
53 case svn_client_diff_summarize_kind_deleted:
54 return 'D';
56 default:
57 return ' ';
61 /* Convert KIND into a word describing the kind to the user. */
62 static const char *
63 kind_to_word(svn_client_diff_summarize_kind_t kind)
65 switch (kind)
67 case svn_client_diff_summarize_kind_modified: return "modified";
68 case svn_client_diff_summarize_kind_added: return "added";
69 case svn_client_diff_summarize_kind_deleted: return "deleted";
70 default: return "none";
74 /* Print summary information about a given change as XML, implements the
75 * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *'
76 * representing the either the path to the working copy root or the url
77 * the path the working copy root corresponds to. */
78 static svn_error_t *
79 summarize_xml(const svn_client_diff_summarize_t *summary,
80 void *baton,
81 apr_pool_t *pool)
83 /* Full path to the object being diffed. This is created by taking the
84 * baton, and appending the target's relative path. */
85 const char *path = baton;
86 svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
88 /* Tack on the target path, so we can differentiate between different parts
89 * of the output when we're given multiple targets. */
90 path = svn_path_join(path, summary->path, pool);
92 /* Convert non-urls to local style, so that things like "" show up as "." */
93 if (! svn_path_is_url(path))
94 path = svn_path_local_style(path, pool);
96 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
97 "kind", svn_cl__node_kind_str(summary->node_kind),
98 "item", kind_to_word(summary->summarize_kind),
99 "props", summary->prop_changed ? "modified" : "none",
100 NULL);
102 svn_xml_escape_cdata_cstring(&sb, path, pool);
103 svn_xml_make_close_tag(&sb, pool, "path");
105 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
107 return SVN_NO_ERROR;
110 /* Print summary information about a given change, implements the
111 * svn_client_diff_summarize_func_t interface. */
112 static svn_error_t *
113 summarize_regular(const svn_client_diff_summarize_t *summary,
114 void *baton,
115 apr_pool_t *pool)
117 const char *path = baton;
119 /* Tack on the target path, so we can differentiate between different parts
120 * of the output when we're given multiple targets. */
121 path = svn_path_join(path, summary->path, pool);
123 /* Convert non-urls to local style, so that things like "" show up as "." */
124 if (! svn_path_is_url(path))
125 path = svn_path_local_style(path, pool);
127 /* Note: This output format tries to look like the output of 'svn status',
128 * thus the blank spaces where information that is not relevant to
129 * a diff summary would go. */
131 SVN_ERR(svn_cmdline_printf(pool,
132 "%c%c %s\n",
133 kind_to_char(summary->summarize_kind),
134 summary->prop_changed ? 'M' : ' ',
135 path));
137 SVN_ERR(svn_cmdline_fflush(stdout));
139 return SVN_NO_ERROR;
142 /* An svn_opt_subcommand_t to handle the 'diff' command.
143 This implements the `svn_opt_subcommand_t' interface. */
144 svn_error_t *
145 svn_cl__diff(apr_getopt_t *os,
146 void *baton,
147 apr_pool_t *pool)
149 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
150 apr_array_header_t *options;
151 apr_array_header_t *targets;
152 apr_file_t *outfile, *errfile;
153 apr_status_t status;
154 const char *old_target, *new_target;
155 apr_pool_t *iterpool;
156 svn_boolean_t pegged_diff = FALSE;
157 int i;
158 const svn_client_diff_summarize_func_t summarize_func =
159 (opt_state->xml ? summarize_xml : summarize_regular);
161 /* Fall back to "" to get options initialized either way. */
163 const char *optstr = opt_state->extensions ? opt_state->extensions : "";
164 options = svn_cstring_split(optstr, " \t\n\r", TRUE, pool);
167 /* Get an apr_file_t representing stdout and stderr, which is where
168 we'll have the external 'diff' program print to. */
169 if ((status = apr_file_open_stdout(&outfile, pool)))
170 return svn_error_wrap_apr(status, _("Can't open stdout"));
171 if ((status = apr_file_open_stderr(&errfile, pool)))
172 return svn_error_wrap_apr(status, _("Can't open stderr"));
174 if (opt_state->xml)
176 svn_stringbuf_t *sb;
178 /* Check that the --summarize is passed as well. */
179 if (!opt_state->summarize)
180 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
181 _("'--xml' option only valid with "
182 "'--summarize' option"));
184 SVN_ERR(svn_cl__xml_print_header("diff", pool));
186 sb = svn_stringbuf_create("", pool);
187 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL);
188 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
191 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
192 opt_state->targets,
193 pool));
195 if (! opt_state->old_target && ! opt_state->new_target
196 && (targets->nelts == 2)
197 && svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
198 && svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *))
199 && opt_state->start_revision.kind == svn_opt_revision_unspecified
200 && opt_state->end_revision.kind == svn_opt_revision_unspecified)
202 /* The 'svn diff OLD_URL[@OLDREV] NEW_URL[@NEWREV]' case matches. */
204 SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
205 APR_ARRAY_IDX(targets, 0, const char *),
206 pool));
207 SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
208 APR_ARRAY_IDX(targets, 1, const char *),
209 pool));
210 targets->nelts = 0;
212 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
213 opt_state->start_revision.kind = svn_opt_revision_head;
214 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
215 opt_state->end_revision.kind = svn_opt_revision_head;
217 else if (opt_state->old_target)
219 apr_array_header_t *tmp, *tmp2;
220 svn_opt_revision_t old_rev, new_rev;
222 /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
223 [PATH...]' case matches. */
225 tmp = apr_array_make(pool, 2, sizeof(const char *));
226 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
227 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
228 ? opt_state->new_target
229 : APR_ARRAY_IDX(tmp, 0,
230 const char *));
232 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
233 pool));
234 SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
235 APR_ARRAY_IDX(tmp2, 0, const char *),
236 pool));
237 if (old_rev.kind != svn_opt_revision_unspecified)
238 opt_state->start_revision = old_rev;
239 SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
240 APR_ARRAY_IDX(tmp2, 1, const char *),
241 pool));
242 if (new_rev.kind != svn_opt_revision_unspecified)
243 opt_state->end_revision = new_rev;
245 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
246 opt_state->start_revision.kind = svn_path_is_url(old_target)
247 ? svn_opt_revision_head : svn_opt_revision_base;
249 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
250 opt_state->end_revision.kind = svn_path_is_url(new_target)
251 ? svn_opt_revision_head : svn_opt_revision_working;
253 else if (opt_state->new_target)
255 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
256 _("'--new' option only valid with "
257 "'--old' option"));
259 else
261 svn_boolean_t working_copy_present = FALSE, url_present = FALSE;
263 /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
265 /* Here each target is a pegged object. Find out the starting
266 and ending paths for each target. */
268 svn_opt_push_implicit_dot_target(targets, pool);
270 old_target = "";
271 new_target = "";
273 /* Check to see if at least one of our paths is a working copy
274 path. */
275 for (i = 0; i < targets->nelts; ++i)
277 const char *path = APR_ARRAY_IDX(targets, i, const char *);
278 if (! svn_path_is_url(path))
279 working_copy_present = TRUE;
280 else
281 url_present = TRUE;
284 if (url_present && working_copy_present)
285 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
286 _("Target lists to diff may not contain "
287 "both working copy paths and URLs"));
289 if (opt_state->start_revision.kind == svn_opt_revision_unspecified
290 && working_copy_present)
291 opt_state->start_revision.kind = svn_opt_revision_base;
292 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
293 opt_state->end_revision.kind = working_copy_present
294 ? svn_opt_revision_working : svn_opt_revision_head;
296 /* Determine if we need to do pegged diffs. */
297 if ((opt_state->start_revision.kind != svn_opt_revision_base
298 && opt_state->start_revision.kind != svn_opt_revision_working)
299 || (opt_state->end_revision.kind != svn_opt_revision_base
300 && opt_state->end_revision.kind != svn_opt_revision_working))
301 pegged_diff = TRUE;
305 svn_opt_push_implicit_dot_target(targets, pool);
307 iterpool = svn_pool_create(pool);
309 for (i = 0; i < targets->nelts; ++i)
311 const char *path = APR_ARRAY_IDX(targets, i, const char *);
312 const char *target1, *target2;
314 svn_pool_clear(iterpool);
315 if (! pegged_diff)
317 target1 = svn_path_join(old_target, path, iterpool);
318 target2 = svn_path_join(new_target, path, iterpool);
320 if (opt_state->summarize)
321 SVN_ERR(svn_client_diff_summarize2
322 (target1,
323 &opt_state->start_revision,
324 target2,
325 &opt_state->end_revision,
326 opt_state->depth,
327 opt_state->notice_ancestry ? FALSE : TRUE,
328 opt_state->changelists,
329 summarize_func,
330 (void *) target1,
331 ((svn_cl__cmd_baton_t *)baton)->ctx,
332 iterpool));
333 else
334 SVN_ERR(svn_client_diff4
335 (options,
336 target1,
337 &(opt_state->start_revision),
338 target2,
339 &(opt_state->end_revision),
340 NULL,
341 opt_state->depth,
342 opt_state->notice_ancestry ? FALSE : TRUE,
343 opt_state->no_diff_deleted,
344 opt_state->force,
345 svn_cmdline_output_encoding(pool),
346 outfile,
347 errfile,
348 opt_state->changelists,
349 ((svn_cl__cmd_baton_t *)baton)->ctx,
350 iterpool));
352 else
354 const char *truepath;
355 svn_opt_revision_t peg_revision;
357 /* First check for a peg revision. */
358 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
359 iterpool));
361 /* Set the default peg revision if one was not specified. */
362 if (peg_revision.kind == svn_opt_revision_unspecified)
363 peg_revision.kind = svn_path_is_url(path)
364 ? svn_opt_revision_head : svn_opt_revision_working;
366 if (opt_state->summarize)
367 SVN_ERR(svn_client_diff_summarize_peg2
368 (truepath,
369 &peg_revision,
370 &opt_state->start_revision,
371 &opt_state->end_revision,
372 opt_state->depth,
373 opt_state->notice_ancestry ? FALSE : TRUE,
374 opt_state->changelists,
375 summarize_func,
376 (void *) truepath,
377 ((svn_cl__cmd_baton_t *)baton)->ctx,
378 iterpool));
379 else
380 SVN_ERR(svn_client_diff_peg4
381 (options,
382 truepath,
383 &peg_revision,
384 &opt_state->start_revision,
385 &opt_state->end_revision,
386 NULL,
387 opt_state->depth,
388 opt_state->notice_ancestry ? FALSE : TRUE,
389 opt_state->no_diff_deleted,
390 opt_state->force,
391 svn_cmdline_output_encoding(pool),
392 outfile,
393 errfile,
394 opt_state->changelists,
395 ((svn_cl__cmd_baton_t *)baton)->ctx,
396 iterpool));
400 if (opt_state->xml)
402 svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
403 svn_xml_make_close_tag(&sb, pool, "paths");
404 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
405 SVN_ERR(svn_cl__xml_print_footer("diff", pool));
408 svn_pool_destroy(iterpool);
410 return SVN_NO_ERROR;