2 * diff-cmd.c -- Display context diff of a file
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
30 #include "svn_pools.h"
31 #include "svn_client.h"
32 #include "svn_string.h"
33 #include "svn_dirent_uri.h"
35 #include "svn_error_codes.h"
36 #include "svn_error.h"
37 #include "svn_types.h"
38 #include "svn_cmdline.h"
42 #include "svn_private_config.h"
47 /* Convert KIND into a single character for display to the user. */
49 kind_to_char(svn_client_diff_summarize_kind_t kind)
53 case svn_client_diff_summarize_kind_modified:
56 case svn_client_diff_summarize_kind_added:
59 case svn_client_diff_summarize_kind_deleted:
67 /* Convert KIND into a word describing the kind to the user. */
69 kind_to_word(svn_client_diff_summarize_kind_t kind)
73 case svn_client_diff_summarize_kind_modified: return "modified";
74 case svn_client_diff_summarize_kind_added: return "added";
75 case svn_client_diff_summarize_kind_deleted: return "deleted";
76 default: return "none";
80 /* Baton for summarize_xml and summarize_regular */
81 struct summarize_baton_t
86 /* Print summary information about a given change as XML, implements the
87 * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *'
88 * representing the either the path to the working copy root or the url
89 * the path the working copy root corresponds to. */
91 summarize_xml(const svn_client_diff_summarize_t *summary,
95 struct summarize_baton_t *b = baton;
96 /* Full path to the object being diffed. This is created by taking the
97 * baton, and appending the target's relative path. */
98 const char *path = b->anchor;
99 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
101 /* Tack on the target path, so we can differentiate between different parts
102 * of the output when we're given multiple targets. */
103 if (svn_path_is_url(path))
105 path = svn_path_url_add_component2(path, summary->path, pool);
109 path = svn_dirent_join(path, summary->path, pool);
111 /* Convert non-urls to local style, so that things like ""
113 path = svn_dirent_local_style(path, pool);
116 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
117 "kind", svn_cl__node_kind_str_xml(summary->node_kind),
118 "item", kind_to_word(summary->summarize_kind),
119 "props", summary->prop_changed ? "modified" : "none",
122 svn_xml_escape_cdata_cstring(&sb, path, pool);
123 svn_xml_make_close_tag(&sb, pool, "path");
125 return svn_cl__error_checked_fputs(sb->data, stdout);
128 /* Print summary information about a given change, implements the
129 * svn_client_diff_summarize_func_t interface. */
131 summarize_regular(const svn_client_diff_summarize_t *summary,
135 struct summarize_baton_t *b = baton;
136 const char *path = b->anchor;
138 /* Tack on the target path, so we can differentiate between different parts
139 * of the output when we're given multiple targets. */
140 if (svn_path_is_url(path))
142 path = svn_path_url_add_component2(path, summary->path, pool);
146 path = svn_dirent_join(path, summary->path, pool);
148 /* Convert non-urls to local style, so that things like ""
150 path = svn_dirent_local_style(path, pool);
153 /* Note: This output format tries to look like the output of 'svn status',
154 * thus the blank spaces where information that is not relevant to
155 * a diff summary would go. */
157 SVN_ERR(svn_cmdline_printf(pool,
159 kind_to_char(summary->summarize_kind),
160 summary->prop_changed ? 'M' : ' ',
163 return svn_cmdline_fflush(stdout);
166 /* An svn_opt_subcommand_t to handle the 'diff' command.
167 This implements the `svn_opt_subcommand_t' interface. */
169 svn_cl__diff(apr_getopt_t *os,
173 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
174 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
175 apr_array_header_t *options;
176 apr_array_header_t *targets;
177 svn_stream_t *outstream;
178 svn_stream_t *errstream;
179 const char *old_target, *new_target;
180 apr_pool_t *iterpool;
181 svn_boolean_t pegged_diff = FALSE;
182 svn_boolean_t show_copies_as_adds =
183 opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds;
184 svn_boolean_t ignore_properties =
185 opt_state->diff.patch_compatible || opt_state->diff.ignore_properties;
187 struct summarize_baton_t summarize_baton;
188 const svn_client_diff_summarize_func_t summarize_func =
189 (opt_state->xml ? summarize_xml : summarize_regular);
191 if (opt_state->extensions)
192 options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
196 /* Get streams representing stdout and stderr, which is where
197 we'll have the external 'diff' program print to. */
198 SVN_ERR(svn_stream_for_stdout(&outstream, pool));
199 SVN_ERR(svn_stream_for_stderr(&errstream, pool));
205 /* Check that the --summarize is passed as well. */
206 if (!opt_state->diff.summarize)
207 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
208 _("'--xml' option only valid with "
209 "'--summarize' option"));
211 SVN_ERR(svn_cl__xml_print_header("diff", pool));
213 sb = svn_stringbuf_create_empty(pool);
214 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL);
215 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
218 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
222 if (! opt_state->old_target && ! opt_state->new_target
223 && (targets->nelts == 2)
224 && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
225 || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))
226 && opt_state->start_revision.kind == svn_opt_revision_unspecified
227 && opt_state->end_revision.kind == svn_opt_revision_unspecified)
229 /* A 2-target diff where one or both targets are URLs. These are
230 * shorthands for some 'svn diff --old X --new Y' invocations. */
232 SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
233 APR_ARRAY_IDX(targets, 0, const char *),
235 SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
236 APR_ARRAY_IDX(targets, 1, const char *),
240 /* Set default start/end revisions based on target types, in the same
241 * manner as done for the corresponding '--old X --new Y' cases,
242 * (note that we have an explicit --new target) */
243 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
244 opt_state->start_revision.kind = svn_path_is_url(old_target)
245 ? svn_opt_revision_head : svn_opt_revision_working;
247 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
248 opt_state->end_revision.kind = svn_path_is_url(new_target)
249 ? svn_opt_revision_head : svn_opt_revision_working;
251 else if (opt_state->old_target)
253 apr_array_header_t *tmp, *tmp2;
254 svn_opt_revision_t old_rev, new_rev;
256 /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
257 [PATH...]' case matches. */
259 tmp = apr_array_make(pool, 2, sizeof(const char *));
260 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
261 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
262 ? opt_state->new_target
263 : opt_state->old_target);
265 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
268 /* Check if either or both targets were skipped (e.g. because they
269 * were .svn directories). */
271 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
273 SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
274 APR_ARRAY_IDX(tmp2, 0, const char *),
276 if (old_rev.kind != svn_opt_revision_unspecified)
277 opt_state->start_revision = old_rev;
278 SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
279 APR_ARRAY_IDX(tmp2, 1, const char *),
281 if (new_rev.kind != svn_opt_revision_unspecified)
282 opt_state->end_revision = new_rev;
284 /* For URLs, default to HEAD. For WC paths, default to WORKING if
285 * new target is explicit; if new target is implicitly the same as
286 * old target, then default the old to BASE and new to WORKING. */
287 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
288 opt_state->start_revision.kind = svn_path_is_url(old_target)
289 ? svn_opt_revision_head
290 : (opt_state->new_target
291 ? svn_opt_revision_working : svn_opt_revision_base);
292 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
293 opt_state->end_revision.kind = svn_path_is_url(new_target)
294 ? svn_opt_revision_head : svn_opt_revision_working;
296 else if (opt_state->new_target)
298 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
299 _("'--new' option only valid with "
304 svn_boolean_t working_copy_present;
306 /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
308 /* Here each target is a pegged object. Find out the starting
309 and ending paths for each target. */
311 svn_opt_push_implicit_dot_target(targets, pool);
316 SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets),
317 _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed "
318 "target types. Try using the --old and --new options or one of "
319 "the shorthand invocations listed in 'svn help diff'."));
321 working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
324 if (opt_state->start_revision.kind == svn_opt_revision_unspecified
325 && working_copy_present)
326 opt_state->start_revision.kind = svn_opt_revision_base;
327 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
328 opt_state->end_revision.kind = working_copy_present
329 ? svn_opt_revision_working : svn_opt_revision_head;
331 /* Determine if we need to do pegged diffs. */
332 if ((opt_state->start_revision.kind != svn_opt_revision_base
333 && opt_state->start_revision.kind != svn_opt_revision_working)
334 || (opt_state->end_revision.kind != svn_opt_revision_base
335 && opt_state->end_revision.kind != svn_opt_revision_working))
340 svn_opt_push_implicit_dot_target(targets, pool);
342 iterpool = svn_pool_create(pool);
344 for (i = 0; i < targets->nelts; ++i)
346 const char *path = APR_ARRAY_IDX(targets, i, const char *);
347 const char *target1, *target2;
349 svn_pool_clear(iterpool);
352 /* We can't be tacking URLs onto base paths! */
353 if (svn_path_is_url(path))
354 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
355 _("Path '%s' not relative to base URLs"),
358 if (svn_path_is_url(old_target))
359 target1 = svn_path_url_add_component2(
361 svn_relpath_canonicalize(path, iterpool),
364 target1 = svn_dirent_join(old_target, path, iterpool);
366 if (svn_path_is_url(new_target))
367 target2 = svn_path_url_add_component2(
369 svn_relpath_canonicalize(path, iterpool),
372 target2 = svn_dirent_join(new_target, path, iterpool);
374 if (opt_state->diff.summarize)
376 summarize_baton.anchor = target1;
378 SVN_ERR(svn_client_diff_summarize2(
380 &opt_state->start_revision,
382 &opt_state->end_revision,
384 ! opt_state->diff.notice_ancestry,
385 opt_state->changelists,
386 summarize_func, &summarize_baton,
390 SVN_ERR(svn_client_diff6(
393 &(opt_state->start_revision),
395 &(opt_state->end_revision),
398 ! opt_state->diff.notice_ancestry,
399 opt_state->diff.no_diff_added,
400 opt_state->diff.no_diff_deleted,
404 opt_state->diff.properties_only,
405 opt_state->diff.use_git_diff_format,
406 svn_cmdline_output_encoding(pool),
409 opt_state->changelists,
414 const char *truepath;
415 svn_opt_revision_t peg_revision;
417 /* First check for a peg revision. */
418 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
421 /* Set the default peg revision if one was not specified. */
422 if (peg_revision.kind == svn_opt_revision_unspecified)
423 peg_revision.kind = svn_path_is_url(path)
424 ? svn_opt_revision_head : svn_opt_revision_working;
426 if (opt_state->diff.summarize)
428 summarize_baton.anchor = truepath;
429 SVN_ERR(svn_client_diff_summarize_peg2(
432 &opt_state->start_revision,
433 &opt_state->end_revision,
435 ! opt_state->diff.notice_ancestry,
436 opt_state->changelists,
437 summarize_func, &summarize_baton,
441 SVN_ERR(svn_client_diff_peg6(
445 &opt_state->start_revision,
446 &opt_state->end_revision,
449 ! opt_state->diff.notice_ancestry,
450 opt_state->diff.no_diff_added,
451 opt_state->diff.no_diff_deleted,
455 opt_state->diff.properties_only,
456 opt_state->diff.use_git_diff_format,
457 svn_cmdline_output_encoding(pool),
460 opt_state->changelists,
467 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
468 svn_xml_make_close_tag(&sb, pool, "paths");
469 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
470 SVN_ERR(svn_cl__xml_print_footer("diff", pool));
473 svn_pool_destroy(iterpool);