]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svn/diff-cmd.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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
237   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
238                                                       opt_state->targets,
239                                                       ctx, FALSE, pool));
240
241   if (! opt_state->old_target && ! opt_state->new_target
242       && (targets->nelts == 2)
243       && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
244           || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))
245       && opt_state->start_revision.kind == svn_opt_revision_unspecified
246       && opt_state->end_revision.kind == svn_opt_revision_unspecified)
247     {
248       /* A 2-target diff where one or both targets are URLs. These are
249        * shorthands for some 'svn diff --old X --new Y' invocations. */
250
251       SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
252                                  APR_ARRAY_IDX(targets, 0, const char *),
253                                  pool));
254       SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
255                                  APR_ARRAY_IDX(targets, 1, const char *),
256                                  pool));
257       targets->nelts = 0;
258
259       /* Set default start/end revisions based on target types, in the same
260        * manner as done for the corresponding '--old X --new Y' cases,
261        * (note that we have an explicit --new target) */
262       if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
263         opt_state->start_revision.kind = svn_path_is_url(old_target)
264             ? svn_opt_revision_head : svn_opt_revision_working;
265
266       if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
267         opt_state->end_revision.kind = svn_path_is_url(new_target)
268             ? svn_opt_revision_head : svn_opt_revision_working;
269     }
270   else if (opt_state->old_target)
271     {
272       apr_array_header_t *tmp, *tmp2;
273       svn_opt_revision_t old_rev, new_rev;
274
275       /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
276          [PATH...]' case matches. */
277
278       tmp = apr_array_make(pool, 2, sizeof(const char *));
279       APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
280       APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
281                                            ? opt_state->new_target
282                                            : opt_state->old_target);
283
284       SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
285                                                           ctx, FALSE, pool));
286
287       /* Check if either or both targets were skipped (e.g. because they
288        * were .svn directories). */
289       if (tmp2->nelts < 2)
290         return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
291
292       SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
293                                  APR_ARRAY_IDX(tmp2, 0, const char *),
294                                  pool));
295       if (old_rev.kind != svn_opt_revision_unspecified)
296         opt_state->start_revision = old_rev;
297       SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
298                                  APR_ARRAY_IDX(tmp2, 1, const char *),
299                                  pool));
300       if (new_rev.kind != svn_opt_revision_unspecified)
301         opt_state->end_revision = new_rev;
302
303       /* For URLs, default to HEAD. For WC paths, default to WORKING if
304        * new target is explicit; if new target is implicitly the same as
305        * old target, then default the old to BASE and new to WORKING. */
306       if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
307         opt_state->start_revision.kind = svn_path_is_url(old_target)
308           ? svn_opt_revision_head
309           : (opt_state->new_target
310              ? svn_opt_revision_working : svn_opt_revision_base);
311       if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
312         opt_state->end_revision.kind = svn_path_is_url(new_target)
313           ? svn_opt_revision_head : svn_opt_revision_working;
314     }
315   else if (opt_state->new_target)
316     {
317       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
318                               _("'--new' option only valid with "
319                                 "'--old' option"));
320     }
321   else
322     {
323       svn_boolean_t working_copy_present;
324
325       /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
326
327       /* Here each target is a pegged object. Find out the starting
328          and ending paths for each target. */
329
330       svn_opt_push_implicit_dot_target(targets, pool);
331
332       old_target = "";
333       new_target = "";
334
335       SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets),
336         _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed "
337           "target types. Try using the --old and --new options or one of "
338           "the shorthand invocations listed in 'svn help diff'."));
339
340       working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
341                                                              const char *));
342
343       if (opt_state->start_revision.kind == svn_opt_revision_unspecified
344           && working_copy_present)
345         opt_state->start_revision.kind = svn_opt_revision_base;
346       if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
347         opt_state->end_revision.kind = working_copy_present
348           ? svn_opt_revision_working : svn_opt_revision_head;
349
350       /* Determine if we need to do pegged diffs. */
351       if ((opt_state->start_revision.kind != svn_opt_revision_base
352            && opt_state->start_revision.kind != svn_opt_revision_working)
353           || (opt_state->end_revision.kind != svn_opt_revision_base
354               && opt_state->end_revision.kind != svn_opt_revision_working))
355         pegged_diff = TRUE;
356
357     }
358
359   /* Should we ignore the content-type when deciding what to diff? */
360   if (opt_state->force)
361     {
362       ignore_content_type = TRUE;
363     }
364   else if (ctx->config)
365     {
366       SVN_ERR(svn_config_get_bool(svn_hash_gets(ctx->config,
367                                                 SVN_CONFIG_CATEGORY_CONFIG),
368                                   &ignore_content_type,
369                                   SVN_CONFIG_SECTION_MISCELLANY,
370                                   SVN_CONFIG_OPTION_DIFF_IGNORE_CONTENT_TYPE,
371                                   FALSE));
372     }
373   else
374     {
375       ignore_content_type = FALSE;
376     }
377
378   svn_opt_push_implicit_dot_target(targets, pool);
379
380   iterpool = svn_pool_create(pool);
381
382   for (i = 0; i < targets->nelts; ++i)
383     {
384       const char *path = APR_ARRAY_IDX(targets, i, const char *);
385       const char *target1, *target2;
386
387       svn_pool_clear(iterpool);
388       if (! pegged_diff)
389         {
390           /* We can't be tacking URLs onto base paths! */
391           if (svn_path_is_url(path))
392             return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
393                                      _("Path '%s' not relative to base URLs"),
394                                      path);
395
396           if (svn_path_is_url(old_target))
397             target1 = svn_path_url_add_component2(
398                           old_target,
399                           svn_relpath_canonicalize(path, iterpool),
400                           iterpool);
401           else
402             target1 = svn_dirent_join(old_target, path, iterpool);
403
404           if (svn_path_is_url(new_target))
405             target2 = svn_path_url_add_component2(
406                           new_target,
407                           svn_relpath_canonicalize(path, iterpool),
408                           iterpool);
409           else
410             target2 = svn_dirent_join(new_target, path, iterpool);
411
412           if (opt_state->diff.summarize)
413             {
414               summarize_baton.anchor = target1;
415               summarize_baton.ignore_properties = ignore_properties;
416
417               SVN_ERR(svn_client_diff_summarize2(
418                                 target1,
419                                 &opt_state->start_revision,
420                                 target2,
421                                 &opt_state->end_revision,
422                                 opt_state->depth,
423                                 ! opt_state->diff.notice_ancestry,
424                                 opt_state->changelists,
425                                 summarize_func, &summarize_baton,
426                                 ctx, iterpool));
427             }
428           else
429             SVN_ERR(svn_client_diff6(
430                      options,
431                      target1,
432                      &(opt_state->start_revision),
433                      target2,
434                      &(opt_state->end_revision),
435                      NULL,
436                      opt_state->depth,
437                      ! opt_state->diff.notice_ancestry,
438                      opt_state->diff.no_diff_added,
439                      opt_state->diff.no_diff_deleted,
440                      show_copies_as_adds,
441                      ignore_content_type,
442                      ignore_properties,
443                      opt_state->diff.properties_only,
444                      opt_state->diff.use_git_diff_format,
445                      svn_cmdline_output_encoding(pool),
446                      outstream,
447                      errstream,
448                      opt_state->changelists,
449                      ctx, iterpool));
450         }
451       else
452         {
453           const char *truepath;
454           svn_opt_revision_t peg_revision;
455
456           /* First check for a peg revision. */
457           SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
458                                      iterpool));
459
460           /* Set the default peg revision if one was not specified. */
461           if (peg_revision.kind == svn_opt_revision_unspecified)
462             peg_revision.kind = svn_path_is_url(path)
463               ? svn_opt_revision_head : svn_opt_revision_working;
464
465           if (opt_state->diff.summarize)
466             {
467               summarize_baton.anchor = truepath;
468               summarize_baton.ignore_properties = ignore_properties;
469               SVN_ERR(svn_client_diff_summarize_peg2(
470                                 truepath,
471                                 &peg_revision,
472                                 &opt_state->start_revision,
473                                 &opt_state->end_revision,
474                                 opt_state->depth,
475                                 ! opt_state->diff.notice_ancestry,
476                                 opt_state->changelists,
477                                 summarize_func, &summarize_baton,
478                                 ctx, iterpool));
479             }
480           else
481             SVN_ERR(svn_client_diff_peg6(
482                      options,
483                      truepath,
484                      &peg_revision,
485                      &opt_state->start_revision,
486                      &opt_state->end_revision,
487                      NULL,
488                      opt_state->depth,
489                      ! opt_state->diff.notice_ancestry,
490                      opt_state->diff.no_diff_added,
491                      opt_state->diff.no_diff_deleted,
492                      show_copies_as_adds,
493                      ignore_content_type,
494                      ignore_properties,
495                      opt_state->diff.properties_only,
496                      opt_state->diff.use_git_diff_format,
497                      svn_cmdline_output_encoding(pool),
498                      outstream,
499                      errstream,
500                      opt_state->changelists,
501                      ctx, iterpool));
502         }
503     }
504
505   if (opt_state->xml)
506     {
507       svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
508       svn_xml_make_close_tag(&sb, pool, "paths");
509       SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
510       SVN_ERR(svn_cl__xml_print_footer("diff", pool));
511     }
512
513   svn_pool_destroy(iterpool);
514
515   return SVN_NO_ERROR;
516 }