]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svn/diff-cmd.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / svn / diff-cmd.c
1 /*
2  * diff-cmd.c -- Display context diff of a file
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24 /* ==================================================================== */
25
26
27 \f
28 /*** Includes. ***/
29
30 #include "svn_pools.h"
31 #include "svn_client.h"
32 #include "svn_string.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_path.h"
35 #include "svn_error_codes.h"
36 #include "svn_error.h"
37 #include "svn_types.h"
38 #include "svn_cmdline.h"
39 #include "svn_xml.h"
40 #include "svn_hash.h"
41 #include "cl.h"
42
43 #include "svn_private_config.h"
44
45 \f
46 /*** Code. ***/
47
48 /* Convert KIND into a single character for display to the user. */
49 static char
50 kind_to_char(svn_client_diff_summarize_kind_t kind)
51 {
52   switch (kind)
53     {
54       case svn_client_diff_summarize_kind_modified:
55         return 'M';
56
57       case svn_client_diff_summarize_kind_added:
58         return 'A';
59
60       case svn_client_diff_summarize_kind_deleted:
61         return 'D';
62
63       default:
64         return ' ';
65     }
66 }
67
68 /* Convert KIND into a word describing the kind to the user. */
69 static const char *
70 kind_to_word(svn_client_diff_summarize_kind_t kind)
71 {
72   switch (kind)
73     {
74       case svn_client_diff_summarize_kind_modified: return "modified";
75       case svn_client_diff_summarize_kind_added:    return "added";
76       case svn_client_diff_summarize_kind_deleted:  return "deleted";
77       default:                                      return "none";
78     }
79 }
80
81 /* Baton for summarize_xml and summarize_regular */
82 struct summarize_baton_t
83 {
84   const char *anchor;
85   svn_boolean_t ignore_properties;
86 };
87
88 /* Print summary information about a given change as XML, implements the
89  * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *'
90  * representing the either the path to the working copy root or the url
91  * the path the working copy root corresponds to. */
92 static svn_error_t *
93 summarize_xml(const svn_client_diff_summarize_t *summary,
94               void *baton,
95               apr_pool_t *pool)
96 {
97   struct summarize_baton_t *b = baton;
98   /* Full path to the object being diffed.  This is created by taking the
99    * baton, and appending the target's relative path. */
100   const char *path = b->anchor;
101   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
102   const char *prop_change;
103
104   if (b->ignore_properties &&
105       summary->summarize_kind == svn_client_diff_summarize_kind_normal)
106     return SVN_NO_ERROR;
107
108   /* Tack on the target path, so we can differentiate between different parts
109    * of the output when we're given multiple targets. */
110   if (svn_path_is_url(path))
111     {
112       path = svn_path_url_add_component2(path, summary->path, pool);
113     }
114   else
115     {
116       path = svn_dirent_join(path, summary->path, pool);
117
118       /* Convert non-urls to local style, so that things like ""
119          show up as "." */
120       path = svn_dirent_local_style(path, pool);
121     }
122
123   prop_change = summary->prop_changed ? "modified" : "none";
124   if (b->ignore_properties)
125     prop_change = "none";
126
127   svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
128                         "kind", svn_cl__node_kind_str_xml(summary->node_kind),
129                         "item", kind_to_word(summary->summarize_kind),
130                         "props",  prop_change,
131                         SVN_VA_NULL);
132
133   svn_xml_escape_cdata_cstring(&sb, path, pool);
134   svn_xml_make_close_tag(&sb, pool, "path");
135
136   return svn_cl__error_checked_fputs(sb->data, stdout);
137 }
138
139 /* Print summary information about a given change, implements the
140  * svn_client_diff_summarize_func_t interface. */
141 static svn_error_t *
142 summarize_regular(const svn_client_diff_summarize_t *summary,
143                   void *baton,
144                   apr_pool_t *pool)
145 {
146   struct summarize_baton_t *b = baton;
147   const char *path = b->anchor;
148   char prop_change;
149
150   if (b->ignore_properties &&
151       summary->summarize_kind == svn_client_diff_summarize_kind_normal)
152     return SVN_NO_ERROR;
153
154   /* Tack on the target path, so we can differentiate between different parts
155    * of the output when we're given multiple targets. */
156   if (svn_path_is_url(path))
157     {
158       path = svn_path_url_add_component2(path, summary->path, pool);
159     }
160   else
161     {
162       path = svn_dirent_join(path, summary->path, pool);
163
164       /* Convert non-urls to local style, so that things like ""
165          show up as "." */
166       path = svn_dirent_local_style(path, pool);
167     }
168
169   /* Note: This output format tries to look like the output of 'svn status',
170    *       thus the blank spaces where information that is not relevant to
171    *       a diff summary would go. */
172
173   prop_change = summary->prop_changed ? 'M' : ' ';
174   if (b->ignore_properties)
175     prop_change = ' ';
176
177   SVN_ERR(svn_cmdline_printf(pool, "%c%c      %s\n",
178                              kind_to_char(summary->summarize_kind),
179                              prop_change, path));
180
181   return svn_cmdline_fflush(stdout);
182 }
183
184 /* An svn_opt_subcommand_t to handle the 'diff' command.
185    This implements the `svn_opt_subcommand_t' interface. */
186 svn_error_t *
187 svn_cl__diff(apr_getopt_t *os,
188              void *baton,
189              apr_pool_t *pool)
190 {
191   svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
192   svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
193   apr_array_header_t *options;
194   apr_array_header_t *targets;
195   svn_stream_t *outstream;
196   svn_stream_t *errstream;
197   const char *old_target, *new_target;
198   apr_pool_t *iterpool;
199   svn_boolean_t pegged_diff = FALSE;
200   svn_boolean_t ignore_content_type;
201   svn_boolean_t show_copies_as_adds =
202     opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds;
203   svn_boolean_t ignore_properties =
204     opt_state->diff.patch_compatible || opt_state->diff.ignore_properties;
205   int i;
206   struct summarize_baton_t summarize_baton;
207   const svn_client_diff_summarize_func_t summarize_func =
208     (opt_state->xml ? summarize_xml : summarize_regular);
209
210   if (opt_state->extensions)
211     options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
212   else
213     options = NULL;
214
215   /* Get streams representing stdout and stderr, which is where
216      we'll have the external 'diff' program print to. */
217   SVN_ERR(svn_stream_for_stdout(&outstream, pool));
218   SVN_ERR(svn_stream_for_stderr(&errstream, pool));
219
220   if (opt_state->xml)
221     {
222       svn_stringbuf_t *sb;
223
224       /* Check that the --summarize is passed as well. */
225       if (!opt_state->diff.summarize)
226         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
227                                 _("'--xml' option only valid with "
228                                   "'--summarize' option"));
229
230       SVN_ERR(svn_cl__xml_print_header("diff", pool));
231
232       sb = svn_stringbuf_create_empty(pool);
233       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", SVN_VA_NULL);
234       SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
235     }
236   if (opt_state->diff.summarize)
237     {
238       if (opt_state->diff.use_git_diff_format)
239         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
240                                  _("'%s' not valid with '--summarize' option"),
241                                  "--git");
242       if (opt_state->diff.patch_compatible)
243         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
244                                  _("'%s' not valid with '--summarize' option"),
245                                  "--patch-compatible");
246       if (opt_state->diff.show_copies_as_adds)
247         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
248                                  _("'%s' not valid with '--summarize' option"),
249                                  "--show-copies-as-adds");
250       if (opt_state->diff.internal_diff)
251         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
252                                  _("'%s' not valid with '--summarize' option"),
253                                  "--internal-diff");
254       if (opt_state->diff.diff_cmd)
255         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
256                                  _("'%s' not valid with '--summarize' option"),
257                                  "--diff-cmd");
258       if (opt_state->diff.no_diff_added)
259         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
260                                  _("'%s' not valid with '--summarize' option"),
261                                  "--no-diff-added");
262       if (opt_state->diff.no_diff_deleted)
263         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
264                                  _("'%s' not valid with '--summarize' option"),
265                                  "--no-diff-deleted");
266       if (opt_state->force)
267         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
268                                  _("'%s' not valid with '--summarize' option"),
269                                  "--force");
270       /* Not handling ignore-properties, and properties-only as there should
271          be a patch adding support for these being applied soon */
272     }
273
274   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
275                                                       opt_state->targets,
276                                                       ctx, FALSE, pool));
277
278   if (! opt_state->old_target && ! opt_state->new_target
279       && (targets->nelts == 2)
280       && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
281           || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))
282       && opt_state->start_revision.kind == svn_opt_revision_unspecified
283       && opt_state->end_revision.kind == svn_opt_revision_unspecified)
284     {
285       /* A 2-target diff where one or both targets are URLs. These are
286        * shorthands for some 'svn diff --old X --new Y' invocations. */
287
288       SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
289                                  APR_ARRAY_IDX(targets, 0, const char *),
290                                  pool));
291       SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
292                                  APR_ARRAY_IDX(targets, 1, const char *),
293                                  pool));
294       targets->nelts = 0;
295
296       /* Set default start/end revisions based on target types, in the same
297        * manner as done for the corresponding '--old X --new Y' cases,
298        * (note that we have an explicit --new target) */
299       if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
300         opt_state->start_revision.kind = svn_path_is_url(old_target)
301             ? svn_opt_revision_head : svn_opt_revision_working;
302
303       if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
304         opt_state->end_revision.kind = svn_path_is_url(new_target)
305             ? svn_opt_revision_head : svn_opt_revision_working;
306     }
307   else if (opt_state->old_target)
308     {
309       apr_array_header_t *tmp, *tmp2;
310       svn_opt_revision_t old_rev, new_rev;
311
312       /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
313          [PATH...]' case matches. */
314
315       tmp = apr_array_make(pool, 2, sizeof(const char *));
316       APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
317       APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
318                                            ? opt_state->new_target
319                                            : opt_state->old_target);
320
321       SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
322                                                           ctx, FALSE, pool));
323
324       /* Check if either or both targets were skipped (e.g. because they
325        * were .svn directories). */
326       if (tmp2->nelts < 2)
327         return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
328
329       SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
330                                  APR_ARRAY_IDX(tmp2, 0, const char *),
331                                  pool));
332       if (old_rev.kind != svn_opt_revision_unspecified)
333         opt_state->start_revision = old_rev;
334       SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
335                                  APR_ARRAY_IDX(tmp2, 1, const char *),
336                                  pool));
337       if (new_rev.kind != svn_opt_revision_unspecified)
338         opt_state->end_revision = new_rev;
339
340       /* For URLs, default to HEAD. For WC paths, default to WORKING if
341        * new target is explicit; if new target is implicitly the same as
342        * old target, then default the old to BASE and new to WORKING. */
343       if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
344         opt_state->start_revision.kind = svn_path_is_url(old_target)
345           ? svn_opt_revision_head
346           : (opt_state->new_target
347              ? svn_opt_revision_working : svn_opt_revision_base);
348       if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
349         opt_state->end_revision.kind = svn_path_is_url(new_target)
350           ? svn_opt_revision_head : svn_opt_revision_working;
351     }
352   else if (opt_state->new_target)
353     {
354       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
355                               _("'--new' option only valid with "
356                                 "'--old' option"));
357     }
358   else
359     {
360       svn_boolean_t working_copy_present;
361
362       /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
363
364       /* Here each target is a pegged object. Find out the starting
365          and ending paths for each target. */
366
367       svn_opt_push_implicit_dot_target(targets, pool);
368
369       old_target = "";
370       new_target = "";
371
372       SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets),
373         _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed "
374           "target types. Try using the --old and --new options or one of "
375           "the shorthand invocations listed in 'svn help diff'."));
376
377       working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
378                                                              const char *));
379
380       if (opt_state->start_revision.kind == svn_opt_revision_unspecified
381           && working_copy_present)
382         opt_state->start_revision.kind = svn_opt_revision_base;
383       if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
384         opt_state->end_revision.kind = working_copy_present
385           ? svn_opt_revision_working : svn_opt_revision_head;
386
387       /* Determine if we need to do pegged diffs. */
388       if ((opt_state->start_revision.kind != svn_opt_revision_base
389            && opt_state->start_revision.kind != svn_opt_revision_working)
390           || (opt_state->end_revision.kind != svn_opt_revision_base
391               && opt_state->end_revision.kind != svn_opt_revision_working))
392         pegged_diff = TRUE;
393
394     }
395
396   /* Should we ignore the content-type when deciding what to diff? */
397   if (opt_state->force)
398     {
399       ignore_content_type = TRUE;
400     }
401   else if (ctx->config)
402     {
403       SVN_ERR(svn_config_get_bool(svn_hash_gets(ctx->config,
404                                                 SVN_CONFIG_CATEGORY_CONFIG),
405                                   &ignore_content_type,
406                                   SVN_CONFIG_SECTION_MISCELLANY,
407                                   SVN_CONFIG_OPTION_DIFF_IGNORE_CONTENT_TYPE,
408                                   FALSE));
409     }
410   else
411     {
412       ignore_content_type = FALSE;
413     }
414
415   svn_opt_push_implicit_dot_target(targets, pool);
416
417   iterpool = svn_pool_create(pool);
418
419   for (i = 0; i < targets->nelts; ++i)
420     {
421       const char *path = APR_ARRAY_IDX(targets, i, const char *);
422       const char *target1, *target2;
423
424       svn_pool_clear(iterpool);
425       if (! pegged_diff)
426         {
427           /* We can't be tacking URLs onto base paths! */
428           if (svn_path_is_url(path))
429             return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
430                                      _("Path '%s' not relative to base URLs"),
431                                      path);
432
433           if (svn_path_is_url(old_target))
434             target1 = svn_path_url_add_component2(
435                           old_target,
436                           svn_relpath_canonicalize(path, iterpool),
437                           iterpool);
438           else
439             target1 = svn_dirent_join(old_target, path, iterpool);
440
441           if (svn_path_is_url(new_target))
442             target2 = svn_path_url_add_component2(
443                           new_target,
444                           svn_relpath_canonicalize(path, iterpool),
445                           iterpool);
446           else
447             target2 = svn_dirent_join(new_target, path, iterpool);
448
449           if (opt_state->diff.summarize)
450             {
451               summarize_baton.anchor = target1;
452               summarize_baton.ignore_properties = ignore_properties;
453
454               SVN_ERR(svn_client_diff_summarize2(
455                                 target1,
456                                 &opt_state->start_revision,
457                                 target2,
458                                 &opt_state->end_revision,
459                                 opt_state->depth,
460                                 ! opt_state->diff.notice_ancestry,
461                                 opt_state->changelists,
462                                 summarize_func, &summarize_baton,
463                                 ctx, iterpool));
464             }
465           else
466             SVN_ERR(svn_client_diff6(
467                      options,
468                      target1,
469                      &(opt_state->start_revision),
470                      target2,
471                      &(opt_state->end_revision),
472                      NULL,
473                      opt_state->depth,
474                      ! opt_state->diff.notice_ancestry,
475                      opt_state->diff.no_diff_added,
476                      opt_state->diff.no_diff_deleted,
477                      show_copies_as_adds,
478                      ignore_content_type,
479                      ignore_properties,
480                      opt_state->diff.properties_only,
481                      opt_state->diff.use_git_diff_format,
482                      svn_cmdline_output_encoding(pool),
483                      outstream,
484                      errstream,
485                      opt_state->changelists,
486                      ctx, iterpool));
487         }
488       else
489         {
490           const char *truepath;
491           svn_opt_revision_t peg_revision;
492
493           /* First check for a peg revision. */
494           SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
495                                      iterpool));
496
497           /* Set the default peg revision if one was not specified. */
498           if (peg_revision.kind == svn_opt_revision_unspecified)
499             peg_revision.kind = svn_path_is_url(path)
500               ? svn_opt_revision_head : svn_opt_revision_working;
501
502           if (opt_state->diff.summarize)
503             {
504               summarize_baton.anchor = truepath;
505               summarize_baton.ignore_properties = ignore_properties;
506               SVN_ERR(svn_client_diff_summarize_peg2(
507                                 truepath,
508                                 &peg_revision,
509                                 &opt_state->start_revision,
510                                 &opt_state->end_revision,
511                                 opt_state->depth,
512                                 ! opt_state->diff.notice_ancestry,
513                                 opt_state->changelists,
514                                 summarize_func, &summarize_baton,
515                                 ctx, iterpool));
516             }
517           else
518             SVN_ERR(svn_client_diff_peg6(
519                      options,
520                      truepath,
521                      &peg_revision,
522                      &opt_state->start_revision,
523                      &opt_state->end_revision,
524                      NULL,
525                      opt_state->depth,
526                      ! opt_state->diff.notice_ancestry,
527                      opt_state->diff.no_diff_added,
528                      opt_state->diff.no_diff_deleted,
529                      show_copies_as_adds,
530                      ignore_content_type,
531                      ignore_properties,
532                      opt_state->diff.properties_only,
533                      opt_state->diff.use_git_diff_format,
534                      svn_cmdline_output_encoding(pool),
535                      outstream,
536                      errstream,
537                      opt_state->changelists,
538                      ctx, iterpool));
539         }
540     }
541
542   if (opt_state->xml)
543     {
544       svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
545       svn_xml_make_close_tag(&sb, pool, "paths");
546       SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
547       SVN_ERR(svn_cl__xml_print_footer("diff", pool));
548     }
549
550   svn_pool_destroy(iterpool);
551
552   return SVN_NO_ERROR;
553 }