Fix compiler warning due to missing function prototype.
[svn.git] / subversion / svn / diff-cmd.c
blob015f286ca87a34cf7b64764967fdd4cfb707f3a3
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 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
151 apr_array_header_t *options;
152 apr_array_header_t *targets;
153 apr_file_t *outfile, *errfile;
154 apr_status_t status;
155 const char *old_target, *new_target;
156 apr_pool_t *iterpool;
157 svn_boolean_t pegged_diff = FALSE;
158 int i;
159 const svn_client_diff_summarize_func_t summarize_func =
160 (opt_state->xml ? summarize_xml : summarize_regular);
162 /* Fall back to "" to get options initialized either way. */
164 const char *optstr = opt_state->extensions ? opt_state->extensions : "";
165 options = svn_cstring_split(optstr, " \t\n\r", TRUE, pool);
168 /* Get an apr_file_t representing stdout and stderr, which is where
169 we'll have the external 'diff' program print to. */
170 if ((status = apr_file_open_stdout(&outfile, pool)))
171 return svn_error_wrap_apr(status, _("Can't open stdout"));
172 if ((status = apr_file_open_stderr(&errfile, pool)))
173 return svn_error_wrap_apr(status, _("Can't open stderr"));
175 if (opt_state->xml)
177 svn_stringbuf_t *sb;
179 /* Check that the --summarize is passed as well. */
180 if (!opt_state->summarize)
181 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
182 _("'--xml' option only valid with "
183 "'--summarize' option"));
185 SVN_ERR(svn_cl__xml_print_header("diff", pool));
187 sb = svn_stringbuf_create("", pool);
188 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL);
189 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
192 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
193 opt_state->targets,
194 ctx, pool));
196 if (! opt_state->old_target && ! opt_state->new_target
197 && (targets->nelts == 2)
198 && svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
199 && svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *))
200 && opt_state->start_revision.kind == svn_opt_revision_unspecified
201 && opt_state->end_revision.kind == svn_opt_revision_unspecified)
203 /* The 'svn diff OLD_URL[@OLDREV] NEW_URL[@NEWREV]' case matches. */
205 SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
206 APR_ARRAY_IDX(targets, 0, const char *),
207 pool));
208 SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
209 APR_ARRAY_IDX(targets, 1, const char *),
210 pool));
211 targets->nelts = 0;
213 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
214 opt_state->start_revision.kind = svn_opt_revision_head;
215 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
216 opt_state->end_revision.kind = svn_opt_revision_head;
218 else if (opt_state->old_target)
220 apr_array_header_t *tmp, *tmp2;
221 svn_opt_revision_t old_rev, new_rev;
223 /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
224 [PATH...]' case matches. */
226 tmp = apr_array_make(pool, 2, sizeof(const char *));
227 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
228 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
229 ? opt_state->new_target
230 : APR_ARRAY_IDX(tmp, 0,
231 const char *));
233 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
234 ctx, pool));
235 SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
236 APR_ARRAY_IDX(tmp2, 0, const char *),
237 pool));
238 if (old_rev.kind != svn_opt_revision_unspecified)
239 opt_state->start_revision = old_rev;
240 SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
241 APR_ARRAY_IDX(tmp2, 1, const char *),
242 pool));
243 if (new_rev.kind != svn_opt_revision_unspecified)
244 opt_state->end_revision = new_rev;
246 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
247 opt_state->start_revision.kind = svn_path_is_url(old_target)
248 ? svn_opt_revision_head : svn_opt_revision_base;
250 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
251 opt_state->end_revision.kind = svn_path_is_url(new_target)
252 ? svn_opt_revision_head : svn_opt_revision_working;
254 else if (opt_state->new_target)
256 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
257 _("'--new' option only valid with "
258 "'--old' option"));
260 else
262 svn_boolean_t working_copy_present = FALSE, url_present = FALSE;
264 /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
266 /* Here each target is a pegged object. Find out the starting
267 and ending paths for each target. */
269 svn_opt_push_implicit_dot_target(targets, pool);
271 old_target = "";
272 new_target = "";
274 /* Check to see if at least one of our paths is a working copy
275 path. */
276 for (i = 0; i < targets->nelts; ++i)
278 const char *path = APR_ARRAY_IDX(targets, i, const char *);
279 if (! svn_path_is_url(path))
280 working_copy_present = TRUE;
281 else
282 url_present = TRUE;
285 if (url_present && working_copy_present)
286 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
287 _("Target lists to diff may not contain "
288 "both working copy paths and URLs"));
290 if (opt_state->start_revision.kind == svn_opt_revision_unspecified
291 && working_copy_present)
292 opt_state->start_revision.kind = svn_opt_revision_base;
293 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
294 opt_state->end_revision.kind = working_copy_present
295 ? svn_opt_revision_working : svn_opt_revision_head;
297 /* Determine if we need to do pegged diffs. */
298 if ((opt_state->start_revision.kind != svn_opt_revision_base
299 && opt_state->start_revision.kind != svn_opt_revision_working)
300 || (opt_state->end_revision.kind != svn_opt_revision_base
301 && opt_state->end_revision.kind != svn_opt_revision_working))
302 pegged_diff = TRUE;
306 svn_opt_push_implicit_dot_target(targets, pool);
308 iterpool = svn_pool_create(pool);
310 for (i = 0; i < targets->nelts; ++i)
312 const char *path = APR_ARRAY_IDX(targets, i, const char *);
313 const char *target1, *target2;
315 svn_pool_clear(iterpool);
316 if (! pegged_diff)
318 target1 = svn_path_join(old_target, path, iterpool);
319 target2 = svn_path_join(new_target, path, iterpool);
321 if (opt_state->summarize)
322 SVN_ERR(svn_client_diff_summarize2
323 (target1,
324 &opt_state->start_revision,
325 target2,
326 &opt_state->end_revision,
327 opt_state->depth,
328 opt_state->notice_ancestry ? FALSE : TRUE,
329 opt_state->changelists,
330 summarize_func,
331 (void *) target1,
332 ctx, 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 ctx, iterpool));
351 else
353 const char *truepath;
354 svn_opt_revision_t peg_revision;
356 /* First check for a peg revision. */
357 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
358 iterpool));
360 /* Set the default peg revision if one was not specified. */
361 if (peg_revision.kind == svn_opt_revision_unspecified)
362 peg_revision.kind = svn_path_is_url(path)
363 ? svn_opt_revision_head : svn_opt_revision_working;
365 if (opt_state->summarize)
366 SVN_ERR(svn_client_diff_summarize_peg2
367 (truepath,
368 &peg_revision,
369 &opt_state->start_revision,
370 &opt_state->end_revision,
371 opt_state->depth,
372 opt_state->notice_ancestry ? FALSE : TRUE,
373 opt_state->changelists,
374 summarize_func,
375 (void *) truepath,
376 ctx, iterpool));
377 else
378 SVN_ERR(svn_client_diff_peg4
379 (options,
380 truepath,
381 &peg_revision,
382 &opt_state->start_revision,
383 &opt_state->end_revision,
384 NULL,
385 opt_state->depth,
386 opt_state->notice_ancestry ? FALSE : TRUE,
387 opt_state->no_diff_deleted,
388 opt_state->force,
389 svn_cmdline_output_encoding(pool),
390 outfile,
391 errfile,
392 opt_state->changelists,
393 ctx, iterpool));
397 if (opt_state->xml)
399 svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
400 svn_xml_make_close_tag(&sb, pool, "paths");
401 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
402 SVN_ERR(svn_cl__xml_print_footer("diff", pool));
405 svn_pool_destroy(iterpool);
407 return SVN_NO_ERROR;