]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/svn/diff-cmd.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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 "cl.h"
41
42 #include "svn_private_config.h"
43
44 \f
45 /*** Code. ***/
46
47 /* Convert KIND into a single character for display to the user. */
48 static char
49 kind_to_char(svn_client_diff_summarize_kind_t kind)
50 {
51   switch (kind)
52     {
53       case svn_client_diff_summarize_kind_modified:
54         return 'M';
55
56       case svn_client_diff_summarize_kind_added:
57         return 'A';
58
59       case svn_client_diff_summarize_kind_deleted:
60         return 'D';
61
62       default:
63         return ' ';
64     }
65 }
66
67 /* Convert KIND into a word describing the kind to the user. */
68 static const char *
69 kind_to_word(svn_client_diff_summarize_kind_t kind)
70 {
71   switch (kind)
72     {
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";
77     }
78 }
79
80 /* Baton for summarize_xml and summarize_regular */
81 struct summarize_baton_t
82 {
83   const char *anchor;
84 };
85
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. */
90 static svn_error_t *
91 summarize_xml(const svn_client_diff_summarize_t *summary,
92               void *baton,
93               apr_pool_t *pool)
94 {
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);
100
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))
104     {
105       path = svn_path_url_add_component2(path, summary->path, pool);
106     }
107   else
108     {
109       path = svn_dirent_join(path, summary->path, pool);
110
111       /* Convert non-urls to local style, so that things like ""
112          show up as "." */
113       path = svn_dirent_local_style(path, pool);
114     }
115
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",
120                         NULL);
121
122   svn_xml_escape_cdata_cstring(&sb, path, pool);
123   svn_xml_make_close_tag(&sb, pool, "path");
124
125   return svn_cl__error_checked_fputs(sb->data, stdout);
126 }
127
128 /* Print summary information about a given change, implements the
129  * svn_client_diff_summarize_func_t interface. */
130 static svn_error_t *
131 summarize_regular(const svn_client_diff_summarize_t *summary,
132                   void *baton,
133                   apr_pool_t *pool)
134 {
135   struct summarize_baton_t *b = baton;
136   const char *path = b->anchor;
137
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))
141     {
142       path = svn_path_url_add_component2(path, summary->path, pool);
143     }
144   else
145     {
146       path = svn_dirent_join(path, summary->path, pool);
147
148       /* Convert non-urls to local style, so that things like ""
149          show up as "." */
150       path = svn_dirent_local_style(path, pool);
151     }
152
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. */
156
157   SVN_ERR(svn_cmdline_printf(pool,
158                              "%c%c      %s\n",
159                              kind_to_char(summary->summarize_kind),
160                              summary->prop_changed ? 'M' : ' ',
161                              path));
162
163   return svn_cmdline_fflush(stdout);
164 }
165
166 /* An svn_opt_subcommand_t to handle the 'diff' command.
167    This implements the `svn_opt_subcommand_t' interface. */
168 svn_error_t *
169 svn_cl__diff(apr_getopt_t *os,
170              void *baton,
171              apr_pool_t *pool)
172 {
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;
186   int i;
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);
190
191   if (opt_state->extensions)
192     options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
193   else
194     options = NULL;
195
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));
200
201   if (opt_state->xml)
202     {
203       svn_stringbuf_t *sb;
204
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"));
210
211       SVN_ERR(svn_cl__xml_print_header("diff", pool));
212
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));
216     }
217
218   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
219                                                       opt_state->targets,
220                                                       ctx, FALSE, pool));
221
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)
228     {
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. */
231
232       SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
233                                  APR_ARRAY_IDX(targets, 0, const char *),
234                                  pool));
235       SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
236                                  APR_ARRAY_IDX(targets, 1, const char *),
237                                  pool));
238       targets->nelts = 0;
239
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;
246
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;
250     }
251   else if (opt_state->old_target)
252     {
253       apr_array_header_t *tmp, *tmp2;
254       svn_opt_revision_t old_rev, new_rev;
255
256       /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
257          [PATH...]' case matches. */
258
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);
264
265       SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
266                                                           ctx, FALSE, pool));
267
268       /* Check if either or both targets were skipped (e.g. because they
269        * were .svn directories). */
270       if (tmp2->nelts < 2)
271         return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
272
273       SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
274                                  APR_ARRAY_IDX(tmp2, 0, const char *),
275                                  pool));
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 *),
280                                  pool));
281       if (new_rev.kind != svn_opt_revision_unspecified)
282         opt_state->end_revision = new_rev;
283
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;
295     }
296   else if (opt_state->new_target)
297     {
298       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
299                               _("'--new' option only valid with "
300                                 "'--old' option"));
301     }
302   else
303     {
304       svn_boolean_t working_copy_present;
305
306       /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
307
308       /* Here each target is a pegged object. Find out the starting
309          and ending paths for each target. */
310
311       svn_opt_push_implicit_dot_target(targets, pool);
312
313       old_target = "";
314       new_target = "";
315
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'."));
320
321       working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
322                                                              const char *));
323
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;
330
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))
336         pegged_diff = TRUE;
337
338     }
339
340   svn_opt_push_implicit_dot_target(targets, pool);
341
342   iterpool = svn_pool_create(pool);
343
344   for (i = 0; i < targets->nelts; ++i)
345     {
346       const char *path = APR_ARRAY_IDX(targets, i, const char *);
347       const char *target1, *target2;
348
349       svn_pool_clear(iterpool);
350       if (! pegged_diff)
351         {
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"),
356                                      path);
357
358           if (svn_path_is_url(old_target))
359             target1 = svn_path_url_add_component2(
360                           old_target,
361                           svn_relpath_canonicalize(path, iterpool),
362                           iterpool);
363           else
364             target1 = svn_dirent_join(old_target, path, iterpool);
365
366           if (svn_path_is_url(new_target))
367             target2 = svn_path_url_add_component2(
368                           new_target,
369                           svn_relpath_canonicalize(path, iterpool),
370                           iterpool);
371           else
372             target2 = svn_dirent_join(new_target, path, iterpool);
373
374           if (opt_state->diff.summarize)
375             {
376               summarize_baton.anchor = target1;
377
378               SVN_ERR(svn_client_diff_summarize2(
379                                 target1,
380                                 &opt_state->start_revision,
381                                 target2,
382                                 &opt_state->end_revision,
383                                 opt_state->depth,
384                                 ! opt_state->diff.notice_ancestry,
385                                 opt_state->changelists,
386                                 summarize_func, &summarize_baton,
387                                 ctx, iterpool));
388             }
389           else
390             SVN_ERR(svn_client_diff6(
391                      options,
392                      target1,
393                      &(opt_state->start_revision),
394                      target2,
395                      &(opt_state->end_revision),
396                      NULL,
397                      opt_state->depth,
398                      ! opt_state->diff.notice_ancestry,
399                      opt_state->diff.no_diff_added,
400                      opt_state->diff.no_diff_deleted,
401                      show_copies_as_adds,
402                      opt_state->force,
403                      ignore_properties,
404                      opt_state->diff.properties_only,
405                      opt_state->diff.use_git_diff_format,
406                      svn_cmdline_output_encoding(pool),
407                      outstream,
408                      errstream,
409                      opt_state->changelists,
410                      ctx, iterpool));
411         }
412       else
413         {
414           const char *truepath;
415           svn_opt_revision_t peg_revision;
416
417           /* First check for a peg revision. */
418           SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
419                                      iterpool));
420
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;
425
426           if (opt_state->diff.summarize)
427             {
428               summarize_baton.anchor = truepath;
429               SVN_ERR(svn_client_diff_summarize_peg2(
430                                 truepath,
431                                 &peg_revision,
432                                 &opt_state->start_revision,
433                                 &opt_state->end_revision,
434                                 opt_state->depth,
435                                 ! opt_state->diff.notice_ancestry,
436                                 opt_state->changelists,
437                                 summarize_func, &summarize_baton,
438                                 ctx, iterpool));
439             }
440           else
441             SVN_ERR(svn_client_diff_peg6(
442                      options,
443                      truepath,
444                      &peg_revision,
445                      &opt_state->start_revision,
446                      &opt_state->end_revision,
447                      NULL,
448                      opt_state->depth,
449                      ! opt_state->diff.notice_ancestry,
450                      opt_state->diff.no_diff_added,
451                      opt_state->diff.no_diff_deleted,
452                      show_copies_as_adds,
453                      opt_state->force,
454                      ignore_properties,
455                      opt_state->diff.properties_only,
456                      opt_state->diff.use_git_diff_format,
457                      svn_cmdline_output_encoding(pool),
458                      outstream,
459                      errstream,
460                      opt_state->changelists,
461                      ctx, iterpool));
462         }
463     }
464
465   if (opt_state->xml)
466     {
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));
471     }
472
473   svn_pool_destroy(iterpool);
474
475   return SVN_NO_ERROR;
476 }