1251881Speter/*
2251881Speter * diff-cmd.c -- Display context diff of a file
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include "svn_pools.h"
31251881Speter#include "svn_client.h"
32251881Speter#include "svn_string.h"
33251881Speter#include "svn_dirent_uri.h"
34251881Speter#include "svn_path.h"
35251881Speter#include "svn_error_codes.h"
36251881Speter#include "svn_error.h"
37251881Speter#include "svn_types.h"
38251881Speter#include "svn_cmdline.h"
39251881Speter#include "svn_xml.h"
40251881Speter#include "cl.h"
41251881Speter
42251881Speter#include "svn_private_config.h"
43251881Speter
44251881Speter
45251881Speter/*** Code. ***/
46251881Speter
47251881Speter/* Convert KIND into a single character for display to the user. */
48251881Speterstatic char
49251881Speterkind_to_char(svn_client_diff_summarize_kind_t kind)
50251881Speter{
51251881Speter  switch (kind)
52251881Speter    {
53251881Speter      case svn_client_diff_summarize_kind_modified:
54251881Speter        return 'M';
55251881Speter
56251881Speter      case svn_client_diff_summarize_kind_added:
57251881Speter        return 'A';
58251881Speter
59251881Speter      case svn_client_diff_summarize_kind_deleted:
60251881Speter        return 'D';
61251881Speter
62251881Speter      default:
63251881Speter        return ' ';
64251881Speter    }
65251881Speter}
66251881Speter
67251881Speter/* Convert KIND into a word describing the kind to the user. */
68251881Speterstatic const char *
69251881Speterkind_to_word(svn_client_diff_summarize_kind_t kind)
70251881Speter{
71251881Speter  switch (kind)
72251881Speter    {
73251881Speter      case svn_client_diff_summarize_kind_modified: return "modified";
74251881Speter      case svn_client_diff_summarize_kind_added:    return "added";
75251881Speter      case svn_client_diff_summarize_kind_deleted:  return "deleted";
76251881Speter      default:                                      return "none";
77251881Speter    }
78251881Speter}
79251881Speter
80251881Speter/* Baton for summarize_xml and summarize_regular */
81251881Speterstruct summarize_baton_t
82251881Speter{
83251881Speter  const char *anchor;
84251881Speter};
85251881Speter
86251881Speter/* Print summary information about a given change as XML, implements the
87251881Speter * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *'
88251881Speter * representing the either the path to the working copy root or the url
89251881Speter * the path the working copy root corresponds to. */
90251881Speterstatic svn_error_t *
91251881Spetersummarize_xml(const svn_client_diff_summarize_t *summary,
92251881Speter              void *baton,
93251881Speter              apr_pool_t *pool)
94251881Speter{
95251881Speter  struct summarize_baton_t *b = baton;
96251881Speter  /* Full path to the object being diffed.  This is created by taking the
97251881Speter   * baton, and appending the target's relative path. */
98251881Speter  const char *path = b->anchor;
99251881Speter  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
100251881Speter
101251881Speter  /* Tack on the target path, so we can differentiate between different parts
102251881Speter   * of the output when we're given multiple targets. */
103251881Speter  if (svn_path_is_url(path))
104251881Speter    {
105251881Speter      path = svn_path_url_add_component2(path, summary->path, pool);
106251881Speter    }
107251881Speter  else
108251881Speter    {
109251881Speter      path = svn_dirent_join(path, summary->path, pool);
110251881Speter
111251881Speter      /* Convert non-urls to local style, so that things like ""
112251881Speter         show up as "." */
113251881Speter      path = svn_dirent_local_style(path, pool);
114251881Speter    }
115251881Speter
116251881Speter  svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
117251881Speter                        "kind", svn_cl__node_kind_str_xml(summary->node_kind),
118251881Speter                        "item", kind_to_word(summary->summarize_kind),
119251881Speter                        "props", summary->prop_changed ? "modified" : "none",
120251881Speter                        NULL);
121251881Speter
122251881Speter  svn_xml_escape_cdata_cstring(&sb, path, pool);
123251881Speter  svn_xml_make_close_tag(&sb, pool, "path");
124251881Speter
125251881Speter  return svn_cl__error_checked_fputs(sb->data, stdout);
126251881Speter}
127251881Speter
128251881Speter/* Print summary information about a given change, implements the
129251881Speter * svn_client_diff_summarize_func_t interface. */
130251881Speterstatic svn_error_t *
131251881Spetersummarize_regular(const svn_client_diff_summarize_t *summary,
132251881Speter                  void *baton,
133251881Speter                  apr_pool_t *pool)
134251881Speter{
135251881Speter  struct summarize_baton_t *b = baton;
136251881Speter  const char *path = b->anchor;
137251881Speter
138251881Speter  /* Tack on the target path, so we can differentiate between different parts
139251881Speter   * of the output when we're given multiple targets. */
140251881Speter  if (svn_path_is_url(path))
141251881Speter    {
142251881Speter      path = svn_path_url_add_component2(path, summary->path, pool);
143251881Speter    }
144251881Speter  else
145251881Speter    {
146251881Speter      path = svn_dirent_join(path, summary->path, pool);
147251881Speter
148251881Speter      /* Convert non-urls to local style, so that things like ""
149251881Speter         show up as "." */
150251881Speter      path = svn_dirent_local_style(path, pool);
151251881Speter    }
152251881Speter
153251881Speter  /* Note: This output format tries to look like the output of 'svn status',
154251881Speter   *       thus the blank spaces where information that is not relevant to
155251881Speter   *       a diff summary would go. */
156251881Speter
157251881Speter  SVN_ERR(svn_cmdline_printf(pool,
158251881Speter                             "%c%c      %s\n",
159251881Speter                             kind_to_char(summary->summarize_kind),
160251881Speter                             summary->prop_changed ? 'M' : ' ',
161251881Speter                             path));
162251881Speter
163251881Speter  return svn_cmdline_fflush(stdout);
164251881Speter}
165251881Speter
166251881Speter/* An svn_opt_subcommand_t to handle the 'diff' command.
167251881Speter   This implements the `svn_opt_subcommand_t' interface. */
168251881Spetersvn_error_t *
169251881Spetersvn_cl__diff(apr_getopt_t *os,
170251881Speter             void *baton,
171251881Speter             apr_pool_t *pool)
172251881Speter{
173251881Speter  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
174251881Speter  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
175251881Speter  apr_array_header_t *options;
176251881Speter  apr_array_header_t *targets;
177251881Speter  svn_stream_t *outstream;
178251881Speter  svn_stream_t *errstream;
179251881Speter  const char *old_target, *new_target;
180251881Speter  apr_pool_t *iterpool;
181251881Speter  svn_boolean_t pegged_diff = FALSE;
182251881Speter  svn_boolean_t show_copies_as_adds =
183251881Speter    opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds;
184251881Speter  svn_boolean_t ignore_properties =
185251881Speter    opt_state->diff.patch_compatible || opt_state->diff.ignore_properties;
186251881Speter  int i;
187251881Speter  struct summarize_baton_t summarize_baton;
188251881Speter  const svn_client_diff_summarize_func_t summarize_func =
189251881Speter    (opt_state->xml ? summarize_xml : summarize_regular);
190251881Speter
191251881Speter  if (opt_state->extensions)
192251881Speter    options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
193251881Speter  else
194251881Speter    options = NULL;
195251881Speter
196251881Speter  /* Get streams representing stdout and stderr, which is where
197251881Speter     we'll have the external 'diff' program print to. */
198251881Speter  SVN_ERR(svn_stream_for_stdout(&outstream, pool));
199251881Speter  SVN_ERR(svn_stream_for_stderr(&errstream, pool));
200251881Speter
201251881Speter  if (opt_state->xml)
202251881Speter    {
203251881Speter      svn_stringbuf_t *sb;
204251881Speter
205251881Speter      /* Check that the --summarize is passed as well. */
206251881Speter      if (!opt_state->diff.summarize)
207251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
208251881Speter                                _("'--xml' option only valid with "
209251881Speter                                  "'--summarize' option"));
210251881Speter
211251881Speter      SVN_ERR(svn_cl__xml_print_header("diff", pool));
212251881Speter
213251881Speter      sb = svn_stringbuf_create_empty(pool);
214251881Speter      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL);
215251881Speter      SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
216251881Speter    }
217251881Speter
218251881Speter  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
219251881Speter                                                      opt_state->targets,
220251881Speter                                                      ctx, FALSE, pool));
221251881Speter
222251881Speter  if (! opt_state->old_target && ! opt_state->new_target
223251881Speter      && (targets->nelts == 2)
224251881Speter      && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
225251881Speter          || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))
226251881Speter      && opt_state->start_revision.kind == svn_opt_revision_unspecified
227251881Speter      && opt_state->end_revision.kind == svn_opt_revision_unspecified)
228251881Speter    {
229251881Speter      /* A 2-target diff where one or both targets are URLs. These are
230251881Speter       * shorthands for some 'svn diff --old X --new Y' invocations. */
231251881Speter
232251881Speter      SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
233251881Speter                                 APR_ARRAY_IDX(targets, 0, const char *),
234251881Speter                                 pool));
235251881Speter      SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
236251881Speter                                 APR_ARRAY_IDX(targets, 1, const char *),
237251881Speter                                 pool));
238251881Speter      targets->nelts = 0;
239251881Speter
240251881Speter      /* Set default start/end revisions based on target types, in the same
241251881Speter       * manner as done for the corresponding '--old X --new Y' cases,
242251881Speter       * (note that we have an explicit --new target) */
243251881Speter      if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
244251881Speter        opt_state->start_revision.kind = svn_path_is_url(old_target)
245251881Speter            ? svn_opt_revision_head : svn_opt_revision_working;
246251881Speter
247251881Speter      if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
248251881Speter        opt_state->end_revision.kind = svn_path_is_url(new_target)
249251881Speter            ? svn_opt_revision_head : svn_opt_revision_working;
250251881Speter    }
251251881Speter  else if (opt_state->old_target)
252251881Speter    {
253251881Speter      apr_array_header_t *tmp, *tmp2;
254251881Speter      svn_opt_revision_t old_rev, new_rev;
255251881Speter
256251881Speter      /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
257251881Speter         [PATH...]' case matches. */
258251881Speter
259251881Speter      tmp = apr_array_make(pool, 2, sizeof(const char *));
260251881Speter      APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
261251881Speter      APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
262251881Speter                                           ? opt_state->new_target
263251881Speter                                           : opt_state->old_target);
264251881Speter
265251881Speter      SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
266251881Speter                                                          ctx, FALSE, pool));
267251881Speter
268251881Speter      /* Check if either or both targets were skipped (e.g. because they
269251881Speter       * were .svn directories). */
270251881Speter      if (tmp2->nelts < 2)
271251881Speter        return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
272251881Speter
273251881Speter      SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
274251881Speter                                 APR_ARRAY_IDX(tmp2, 0, const char *),
275251881Speter                                 pool));
276251881Speter      if (old_rev.kind != svn_opt_revision_unspecified)
277251881Speter        opt_state->start_revision = old_rev;
278251881Speter      SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
279251881Speter                                 APR_ARRAY_IDX(tmp2, 1, const char *),
280251881Speter                                 pool));
281251881Speter      if (new_rev.kind != svn_opt_revision_unspecified)
282251881Speter        opt_state->end_revision = new_rev;
283251881Speter
284251881Speter      /* For URLs, default to HEAD. For WC paths, default to WORKING if
285251881Speter       * new target is explicit; if new target is implicitly the same as
286251881Speter       * old target, then default the old to BASE and new to WORKING. */
287251881Speter      if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
288251881Speter        opt_state->start_revision.kind = svn_path_is_url(old_target)
289251881Speter          ? svn_opt_revision_head
290251881Speter          : (opt_state->new_target
291251881Speter             ? svn_opt_revision_working : svn_opt_revision_base);
292251881Speter      if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
293251881Speter        opt_state->end_revision.kind = svn_path_is_url(new_target)
294251881Speter          ? svn_opt_revision_head : svn_opt_revision_working;
295251881Speter    }
296251881Speter  else if (opt_state->new_target)
297251881Speter    {
298251881Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
299251881Speter                              _("'--new' option only valid with "
300251881Speter                                "'--old' option"));
301251881Speter    }
302251881Speter  else
303251881Speter    {
304251881Speter      svn_boolean_t working_copy_present;
305251881Speter
306251881Speter      /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
307251881Speter
308251881Speter      /* Here each target is a pegged object. Find out the starting
309251881Speter         and ending paths for each target. */
310251881Speter
311251881Speter      svn_opt_push_implicit_dot_target(targets, pool);
312251881Speter
313251881Speter      old_target = "";
314251881Speter      new_target = "";
315251881Speter
316251881Speter      SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets),
317251881Speter        _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed "
318251881Speter          "target types. Try using the --old and --new options or one of "
319251881Speter          "the shorthand invocations listed in 'svn help diff'."));
320251881Speter
321251881Speter      working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
322251881Speter                                                             const char *));
323251881Speter
324251881Speter      if (opt_state->start_revision.kind == svn_opt_revision_unspecified
325251881Speter          && working_copy_present)
326251881Speter        opt_state->start_revision.kind = svn_opt_revision_base;
327251881Speter      if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
328251881Speter        opt_state->end_revision.kind = working_copy_present
329251881Speter          ? svn_opt_revision_working : svn_opt_revision_head;
330251881Speter
331251881Speter      /* Determine if we need to do pegged diffs. */
332251881Speter      if ((opt_state->start_revision.kind != svn_opt_revision_base
333251881Speter           && opt_state->start_revision.kind != svn_opt_revision_working)
334251881Speter          || (opt_state->end_revision.kind != svn_opt_revision_base
335251881Speter              && opt_state->end_revision.kind != svn_opt_revision_working))
336251881Speter        pegged_diff = TRUE;
337251881Speter
338251881Speter    }
339251881Speter
340251881Speter  svn_opt_push_implicit_dot_target(targets, pool);
341251881Speter
342251881Speter  iterpool = svn_pool_create(pool);
343251881Speter
344251881Speter  for (i = 0; i < targets->nelts; ++i)
345251881Speter    {
346251881Speter      const char *path = APR_ARRAY_IDX(targets, i, const char *);
347251881Speter      const char *target1, *target2;
348251881Speter
349251881Speter      svn_pool_clear(iterpool);
350251881Speter      if (! pegged_diff)
351251881Speter        {
352251881Speter          /* We can't be tacking URLs onto base paths! */
353251881Speter          if (svn_path_is_url(path))
354251881Speter            return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
355251881Speter                                     _("Path '%s' not relative to base URLs"),
356251881Speter                                     path);
357251881Speter
358251881Speter          if (svn_path_is_url(old_target))
359251881Speter            target1 = svn_path_url_add_component2(
360251881Speter                          old_target,
361251881Speter                          svn_relpath_canonicalize(path, iterpool),
362251881Speter                          iterpool);
363251881Speter          else
364251881Speter            target1 = svn_dirent_join(old_target, path, iterpool);
365251881Speter
366251881Speter          if (svn_path_is_url(new_target))
367251881Speter            target2 = svn_path_url_add_component2(
368251881Speter                          new_target,
369251881Speter                          svn_relpath_canonicalize(path, iterpool),
370251881Speter                          iterpool);
371251881Speter          else
372251881Speter            target2 = svn_dirent_join(new_target, path, iterpool);
373251881Speter
374251881Speter          if (opt_state->diff.summarize)
375251881Speter            {
376251881Speter              summarize_baton.anchor = target1;
377251881Speter
378251881Speter              SVN_ERR(svn_client_diff_summarize2(
379251881Speter                                target1,
380251881Speter                                &opt_state->start_revision,
381251881Speter                                target2,
382251881Speter                                &opt_state->end_revision,
383251881Speter                                opt_state->depth,
384251881Speter                                ! opt_state->diff.notice_ancestry,
385251881Speter                                opt_state->changelists,
386251881Speter                                summarize_func, &summarize_baton,
387251881Speter                                ctx, iterpool));
388251881Speter            }
389251881Speter          else
390251881Speter            SVN_ERR(svn_client_diff6(
391251881Speter                     options,
392251881Speter                     target1,
393251881Speter                     &(opt_state->start_revision),
394251881Speter                     target2,
395251881Speter                     &(opt_state->end_revision),
396251881Speter                     NULL,
397251881Speter                     opt_state->depth,
398251881Speter                     ! opt_state->diff.notice_ancestry,
399251881Speter                     opt_state->diff.no_diff_added,
400251881Speter                     opt_state->diff.no_diff_deleted,
401251881Speter                     show_copies_as_adds,
402251881Speter                     opt_state->force,
403251881Speter                     ignore_properties,
404251881Speter                     opt_state->diff.properties_only,
405251881Speter                     opt_state->diff.use_git_diff_format,
406251881Speter                     svn_cmdline_output_encoding(pool),
407251881Speter                     outstream,
408251881Speter                     errstream,
409251881Speter                     opt_state->changelists,
410251881Speter                     ctx, iterpool));
411251881Speter        }
412251881Speter      else
413251881Speter        {
414251881Speter          const char *truepath;
415251881Speter          svn_opt_revision_t peg_revision;
416251881Speter
417251881Speter          /* First check for a peg revision. */
418251881Speter          SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
419251881Speter                                     iterpool));
420251881Speter
421251881Speter          /* Set the default peg revision if one was not specified. */
422251881Speter          if (peg_revision.kind == svn_opt_revision_unspecified)
423251881Speter            peg_revision.kind = svn_path_is_url(path)
424251881Speter              ? svn_opt_revision_head : svn_opt_revision_working;
425251881Speter
426251881Speter          if (opt_state->diff.summarize)
427251881Speter            {
428251881Speter              summarize_baton.anchor = truepath;
429251881Speter              SVN_ERR(svn_client_diff_summarize_peg2(
430251881Speter                                truepath,
431251881Speter                                &peg_revision,
432251881Speter                                &opt_state->start_revision,
433251881Speter                                &opt_state->end_revision,
434251881Speter                                opt_state->depth,
435251881Speter                                ! opt_state->diff.notice_ancestry,
436251881Speter                                opt_state->changelists,
437251881Speter                                summarize_func, &summarize_baton,
438251881Speter                                ctx, iterpool));
439251881Speter            }
440251881Speter          else
441251881Speter            SVN_ERR(svn_client_diff_peg6(
442251881Speter                     options,
443251881Speter                     truepath,
444251881Speter                     &peg_revision,
445251881Speter                     &opt_state->start_revision,
446251881Speter                     &opt_state->end_revision,
447251881Speter                     NULL,
448251881Speter                     opt_state->depth,
449251881Speter                     ! opt_state->diff.notice_ancestry,
450251881Speter                     opt_state->diff.no_diff_added,
451251881Speter                     opt_state->diff.no_diff_deleted,
452251881Speter                     show_copies_as_adds,
453251881Speter                     opt_state->force,
454251881Speter                     ignore_properties,
455251881Speter                     opt_state->diff.properties_only,
456251881Speter                     opt_state->diff.use_git_diff_format,
457251881Speter                     svn_cmdline_output_encoding(pool),
458251881Speter                     outstream,
459251881Speter                     errstream,
460251881Speter                     opt_state->changelists,
461251881Speter                     ctx, iterpool));
462251881Speter        }
463251881Speter    }
464251881Speter
465251881Speter  if (opt_state->xml)
466251881Speter    {
467251881Speter      svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
468251881Speter      svn_xml_make_close_tag(&sb, pool, "paths");
469251881Speter      SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
470251881Speter      SVN_ERR(svn_cl__xml_print_footer("diff", pool));
471251881Speter    }
472251881Speter
473251881Speter  svn_pool_destroy(iterpool);
474251881Speter
475251881Speter  return SVN_NO_ERROR;
476251881Speter}
477