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_file_t
*outfile
, *errfile
;
154 const char *old_target
, *new_target
;
155 apr_pool_t
*iterpool
;
156 svn_boolean_t pegged_diff
= FALSE
;
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"));
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
,
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 *),
207 SVN_ERR(svn_opt_parse_path(&opt_state
->end_revision
, &new_target
,
208 APR_ARRAY_IDX(targets
, 1, const char *),
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,
232 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2
, os
, tmp
,
234 SVN_ERR(svn_opt_parse_path(&old_rev
, &old_target
,
235 APR_ARRAY_IDX(tmp2
, 0, const char *),
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 *),
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 "
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
);
273 /* Check to see if at least one of our paths is a working copy
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
;
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
))
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
);
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
323 &opt_state
->start_revision
,
325 &opt_state
->end_revision
,
327 opt_state
->notice_ancestry
? FALSE
: TRUE
,
328 opt_state
->changelists
,
331 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
334 SVN_ERR(svn_client_diff4
337 &(opt_state
->start_revision
),
339 &(opt_state
->end_revision
),
342 opt_state
->notice_ancestry
? FALSE
: TRUE
,
343 opt_state
->no_diff_deleted
,
345 svn_cmdline_output_encoding(pool
),
348 opt_state
->changelists
,
349 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
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
,
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
370 &opt_state
->start_revision
,
371 &opt_state
->end_revision
,
373 opt_state
->notice_ancestry
? FALSE
: TRUE
,
374 opt_state
->changelists
,
377 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
380 SVN_ERR(svn_client_diff_peg4
384 &opt_state
->start_revision
,
385 &opt_state
->end_revision
,
388 opt_state
->notice_ancestry
? FALSE
: TRUE
,
389 opt_state
->no_diff_deleted
,
391 svn_cmdline_output_encoding(pool
),
394 opt_state
->changelists
,
395 ((svn_cl__cmd_baton_t
*)baton
)->ctx
,
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
);