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 /* ==================================================================== */
25 #include "svn_pools.h"
26 #include "svn_client.h"
27 #include "svn_string.h"
29 #include "svn_error_codes.h"
30 #include "svn_error.h"
31 #include "svn_types.h"
32 #include "svn_cmdline.h"
36 #include "svn_private_config.h"
41 /* Convert KIND into a single character for display to the user. */
43 kind_to_char(svn_client_diff_summarize_kind_t kind
)
47 case svn_client_diff_summarize_kind_modified
:
50 case svn_client_diff_summarize_kind_added
:
53 case svn_client_diff_summarize_kind_deleted
:
61 /* Convert KIND into a word describing the kind to the user. */
63 kind_to_word(svn_client_diff_summarize_kind_t 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. */
79 summarize_xml(const svn_client_diff_summarize_t
*summary
,
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",
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
));
110 /* Print summary information about a given change, implements the
111 * svn_client_diff_summarize_func_t interface. */
113 summarize_regular(const svn_client_diff_summarize_t
*summary
,
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
,
133 kind_to_char(summary
->summarize_kind
),
134 summary
->prop_changed
? 'M' : ' ',
137 SVN_ERR(svn_cmdline_fflush(stdout
));
142 /* An svn_opt_subcommand_t to handle the 'diff' command.
143 This implements the `svn_opt_subcommand_t' interface. */
145 svn_cl__diff(apr_getopt_t
*os
,
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_array_header_t
*changelist_targets
= NULL
, *combined_targets
= NULL
;
153 apr_file_t
*outfile
, *errfile
;
155 const char *old_target
, *new_target
;
156 apr_pool_t
*iterpool
;
157 svn_boolean_t pegged_diff
= FALSE
;
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"));
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 /* Before allowing svn_opt_args_to_target_array2() to canonicalize
193 all the targets, we need to build a list of targets made of both
194 ones the user typed, as well as any specified by --changelist. */
195 if (opt_state
->changelist
)
197 SVN_ERR(svn_client_get_changelist(&changelist_targets
,
198 opt_state
->changelist
,
200 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
202 if (apr_is_empty_array(changelist_targets
))
203 return svn_error_createf(SVN_ERR_UNKNOWN_CHANGELIST
, NULL
,
204 _("Unknown changelist '%s'"),
205 opt_state
->changelist
);
208 if (opt_state
->targets
&& changelist_targets
)
209 combined_targets
= apr_array_append(pool
, opt_state
->targets
,
211 else if (opt_state
->targets
)
212 combined_targets
= opt_state
->targets
;
213 else if (changelist_targets
)
214 combined_targets
= changelist_targets
;
216 SVN_ERR(svn_opt_args_to_target_array2(&targets
, os
,
217 combined_targets
, pool
));
219 if (! opt_state
->old_target
&& ! opt_state
->new_target
220 && (targets
->nelts
== 2)
221 && svn_path_is_url(APR_ARRAY_IDX(targets
, 0, const char *))
222 && svn_path_is_url(APR_ARRAY_IDX(targets
, 1, const char *))
223 && opt_state
->start_revision
.kind
== svn_opt_revision_unspecified
224 && opt_state
->end_revision
.kind
== svn_opt_revision_unspecified
)
226 /* The 'svn diff OLD_URL[@OLDREV] NEW_URL[@NEWREV]' case matches. */
228 SVN_ERR(svn_opt_parse_path(&opt_state
->start_revision
, &old_target
,
229 APR_ARRAY_IDX(targets
, 0, const char *),
231 SVN_ERR(svn_opt_parse_path(&opt_state
->end_revision
, &new_target
,
232 APR_ARRAY_IDX(targets
, 1, const char *),
236 if (opt_state
->start_revision
.kind
== svn_opt_revision_unspecified
)
237 opt_state
->start_revision
.kind
= svn_opt_revision_head
;
238 if (opt_state
->end_revision
.kind
== svn_opt_revision_unspecified
)
239 opt_state
->end_revision
.kind
= svn_opt_revision_head
;
241 else if (opt_state
->old_target
)
243 apr_array_header_t
*tmp
, *tmp2
;
244 svn_opt_revision_t old_rev
, new_rev
;
246 /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
247 [PATH...]' case matches. */
249 tmp
= apr_array_make(pool
, 2, sizeof(const char *));
250 APR_ARRAY_PUSH(tmp
, const char *) = (opt_state
->old_target
);
251 APR_ARRAY_PUSH(tmp
, const char *) = (opt_state
->new_target
252 ? opt_state
->new_target
253 : APR_ARRAY_IDX(tmp
, 0,
256 SVN_ERR(svn_opt_args_to_target_array2(&tmp2
, os
, tmp
, pool
));
257 SVN_ERR(svn_opt_parse_path(&old_rev
, &old_target
,
258 APR_ARRAY_IDX(tmp2
, 0, const char *),
260 if (old_rev
.kind
!= svn_opt_revision_unspecified
)
261 opt_state
->start_revision
= old_rev
;
262 SVN_ERR(svn_opt_parse_path(&new_rev
, &new_target
,
263 APR_ARRAY_IDX(tmp2
, 1, const char *),
265 if (new_rev
.kind
!= svn_opt_revision_unspecified
)
266 opt_state
->end_revision
= new_rev
;
268 if (opt_state
->start_revision
.kind
== svn_opt_revision_unspecified
)
269 opt_state
->start_revision
.kind
= svn_path_is_url(old_target
)
270 ? svn_opt_revision_head
: svn_opt_revision_base
;
272 if (opt_state
->end_revision
.kind
== svn_opt_revision_unspecified
)
273 opt_state
->end_revision
.kind
= svn_path_is_url(new_target
)
274 ? svn_opt_revision_head
: svn_opt_revision_working
;
276 else if (opt_state
->new_target
)
278 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
279 _("'--new' option only valid with "
284 svn_boolean_t working_copy_present
= FALSE
, url_present
= FALSE
;
286 /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
288 /* Here each target is a pegged object. Find out the starting
289 and ending paths for each target. */
291 svn_opt_push_implicit_dot_target(targets
, pool
);
296 /* Check to see if at least one of our paths is a working copy
298 for (i
= 0; i
< targets
->nelts
; ++i
)
300 const char *path
= APR_ARRAY_IDX(targets
, i
, const char *);
301 if (! svn_path_is_url(path
))
302 working_copy_present
= TRUE
;
307 if (url_present
&& working_copy_present
)
308 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
309 _("Target lists to diff may not contain "
310 "both working copy paths and URLs"));
312 if (opt_state
->start_revision
.kind
== svn_opt_revision_unspecified
313 && working_copy_present
)
314 opt_state
->start_revision
.kind
= svn_opt_revision_base
;
315 if (opt_state
->end_revision
.kind
== svn_opt_revision_unspecified
)
316 opt_state
->end_revision
.kind
= working_copy_present
317 ? svn_opt_revision_working
: svn_opt_revision_head
;
319 /* Determine if we need to do pegged diffs. */
320 if ((opt_state
->start_revision
.kind
!= svn_opt_revision_base
321 && opt_state
->start_revision
.kind
!= svn_opt_revision_working
)
322 || (opt_state
->end_revision
.kind
!= svn_opt_revision_base
323 && opt_state
->end_revision
.kind
!= svn_opt_revision_working
))
328 svn_opt_push_implicit_dot_target(targets
, pool
);
330 iterpool
= svn_pool_create(pool
);
332 for (i
= 0; i
< targets
->nelts
; ++i
)
334 const char *path
= APR_ARRAY_IDX(targets
, i
, const char *);
335 const char *target1
, *target2
;
337 svn_pool_clear(iterpool
);
340 target1
= svn_path_join(old_target
, path
, iterpool
);
341 target2
= svn_path_join(new_target
, path
, iterpool
);
343 if (opt_state
->summarize
)
344 SVN_ERR(svn_client_diff_summarize2
346 &opt_state
->start_revision
,
348 &opt_state
->end_revision
,
350 opt_state
->notice_ancestry
? FALSE
: TRUE
,
353 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
356 SVN_ERR(svn_client_diff4
359 &(opt_state
->start_revision
),
361 &(opt_state
->end_revision
),
364 opt_state
->notice_ancestry
? FALSE
: TRUE
,
365 opt_state
->no_diff_deleted
,
367 svn_cmdline_output_encoding(pool
),
370 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
375 const char *truepath
;
376 svn_opt_revision_t peg_revision
;
378 /* First check for a peg revision. */
379 SVN_ERR(svn_opt_parse_path(&peg_revision
, &truepath
, path
,
382 /* Set the default peg revision if one was not specified. */
383 if (peg_revision
.kind
== svn_opt_revision_unspecified
)
384 peg_revision
.kind
= svn_path_is_url(path
)
385 ? svn_opt_revision_head
: svn_opt_revision_working
;
387 if (opt_state
->summarize
)
388 SVN_ERR(svn_client_diff_summarize_peg2
391 &opt_state
->start_revision
,
392 &opt_state
->end_revision
,
394 opt_state
->notice_ancestry
? FALSE
: TRUE
,
397 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
400 SVN_ERR(svn_client_diff_peg4
404 &opt_state
->start_revision
,
405 &opt_state
->end_revision
,
408 opt_state
->notice_ancestry
? FALSE
: TRUE
,
409 opt_state
->no_diff_deleted
,
411 svn_cmdline_output_encoding(pool
),
414 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
421 svn_stringbuf_t
*sb
= svn_stringbuf_create("", pool
);
422 svn_xml_make_close_tag(&sb
, pool
, "paths");
423 SVN_ERR(svn_cl__error_checked_fputs(sb
->data
, stdout
));
424 SVN_ERR(svn_cl__xml_print_footer("diff", pool
));
427 svn_pool_destroy(iterpool
);