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 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
;
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 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets
, os
,
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 *),
208 SVN_ERR(svn_opt_parse_path(&opt_state
->end_revision
, &new_target
,
209 APR_ARRAY_IDX(targets
, 1, const char *),
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,
233 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2
, os
, tmp
,
235 SVN_ERR(svn_opt_parse_path(&old_rev
, &old_target
,
236 APR_ARRAY_IDX(tmp2
, 0, const char *),
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 *),
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 "
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
);
274 /* Check to see if at least one of our paths is a working copy
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
;
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
))
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
);
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
324 &opt_state
->start_revision
,
326 &opt_state
->end_revision
,
328 opt_state
->notice_ancestry
? FALSE
: TRUE
,
329 opt_state
->changelists
,
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
,
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
,
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
369 &opt_state
->start_revision
,
370 &opt_state
->end_revision
,
372 opt_state
->notice_ancestry
? FALSE
: TRUE
,
373 opt_state
->changelists
,
378 SVN_ERR(svn_client_diff_peg4
382 &opt_state
->start_revision
,
383 &opt_state
->end_revision
,
386 opt_state
->notice_ancestry
? FALSE
: TRUE
,
387 opt_state
->no_diff_deleted
,
389 svn_cmdline_output_encoding(pool
),
392 opt_state
->changelists
,
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
);