1/*
2 * diff_local.c -- A simple diff walker which compares local files against
3 *                 their pristine versions.
4 *
5 * ====================================================================
6 *    Licensed to the Apache Software Foundation (ASF) under one
7 *    or more contributor license agreements.  See the NOTICE file
8 *    distributed with this work for additional information
9 *    regarding copyright ownership.  The ASF licenses this file
10 *    to you under the Apache License, Version 2.0 (the
11 *    "License"); you may not use this file except in compliance
12 *    with the License.  You may obtain a copy of the License at
13 *
14 *      http://www.apache.org/licenses/LICENSE-2.0
15 *
16 *    Unless required by applicable law or agreed to in writing,
17 *    software distributed under the License is distributed on an
18 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 *    KIND, either express or implied.  See the License for the
20 *    specific language governing permissions and limitations
21 *    under the License.
22 * ====================================================================
23 *
24 * This is the simple working copy diff algorithm which is used when you
25 * just use 'svn diff PATH'. It shows what is modified in your working copy
26 * since a node was checked out or copied but doesn't show most kinds of
27 * restructuring operations.
28 *
29 * You can look at this as another form of the status walker.
30 */
31
32#include <apr_hash.h>
33
34#include "svn_error.h"
35#include "svn_pools.h"
36#include "svn_dirent_uri.h"
37#include "svn_path.h"
38#include "svn_hash.h"
39
40#include "private/svn_wc_private.h"
41#include "private/svn_diff_tree.h"
42
43#include "wc.h"
44#include "wc_db.h"
45#include "props.h"
46#include "diff.h"
47
48#include "svn_private_config.h"
49
50/*-------------------------------------------------------------------------*/
51
52/* Baton containing the state of a directory
53   reported open via a diff processor */
54struct node_state_t
55{
56  struct node_state_t *parent;
57
58  apr_pool_t *pool;
59
60  const char *local_abspath;
61  const char *relpath;
62  void *baton;
63
64  svn_diff_source_t *left_src;
65  svn_diff_source_t *right_src;
66  svn_diff_source_t *copy_src;
67
68  svn_boolean_t skip;
69  svn_boolean_t skip_children;
70
71  apr_hash_t *left_props;
72  apr_hash_t *right_props;
73  const apr_array_header_t *propchanges;
74};
75
76/* The diff baton */
77struct diff_baton
78{
79  /* A wc db. */
80  svn_wc__db_t *db;
81
82  /* Report editor paths relative from this directory */
83  const char *anchor_abspath;
84
85  struct node_state_t *cur;
86
87  const svn_diff_tree_processor_t *processor;
88
89  /* Should this diff ignore node ancestry? */
90  svn_boolean_t ignore_ancestry;
91
92  /* Cancel function/baton */
93  svn_cancel_func_t cancel_func;
94  void *cancel_baton;
95
96  apr_pool_t *pool;
97};
98
99/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH
100   is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself,
101   but create it marked with skip+skip_children.
102 */
103static svn_error_t *
104ensure_state(struct diff_baton *eb,
105             const char *local_abspath,
106             svn_boolean_t recursive_skip,
107             apr_pool_t *scratch_pool)
108{
109  struct node_state_t *ns;
110  apr_pool_t *ns_pool;
111  if (!eb->cur)
112    {
113      const char *relpath;
114
115      relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath);
116      if (! relpath)
117        return SVN_NO_ERROR;
118
119      /* Don't recurse on the anchor, as that might loop infinitely because
120            svn_dirent_dirname("/",...)   -> "/"
121            svn_dirent_dirname("C:/",...) -> "C:/" (Windows) */
122      if (*relpath)
123        SVN_ERR(ensure_state(eb,
124                             svn_dirent_dirname(local_abspath, scratch_pool),
125                             FALSE,
126                             scratch_pool));
127    }
128  else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL))
129    SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool),
130                         FALSE,
131                         scratch_pool));
132  else
133    return SVN_NO_ERROR;
134
135  if (eb->cur && eb->cur->skip_children)
136    return SVN_NO_ERROR;
137
138  ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool);
139  ns = apr_pcalloc(ns_pool, sizeof(*ns));
140
141  ns->pool = ns_pool;
142  ns->local_abspath = apr_pstrdup(ns_pool, local_abspath);
143  ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath);
144  ns->parent = eb->cur;
145  eb->cur = ns;
146
147  if (recursive_skip)
148    {
149      ns->skip = TRUE;
150      ns->skip_children = TRUE;
151      return SVN_NO_ERROR;
152    }
153
154  {
155    svn_revnum_t revision;
156    svn_error_t *err;
157
158    err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL,
159                                   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
160                                   NULL, NULL, NULL,
161                                   eb->db, local_abspath,
162                                   scratch_pool, scratch_pool);
163
164    if (err)
165      {
166        if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
167          return svn_error_trace(err);
168        svn_error_clear(err);
169
170        revision = 0; /* Use original revision? */
171      }
172    ns->left_src = svn_diff__source_create(revision, ns->pool);
173    ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool);
174
175    SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip,
176                                      &ns->skip_children,
177                                      ns->relpath,
178                                      ns->left_src,
179                                      ns->right_src,
180                                      NULL /* copyfrom_source */,
181                                      ns->parent ? ns->parent->baton : NULL,
182                                      eb->processor,
183                                      ns->pool, scratch_pool));
184  }
185
186  return SVN_NO_ERROR;
187}
188
189/* Implements svn_wc_status_func3_t */
190static svn_error_t *
191diff_status_callback(void *baton,
192                     const char *local_abspath,
193                     const svn_wc_status3_t *status,
194                     apr_pool_t *scratch_pool)
195{
196  struct diff_baton *eb = baton;
197  svn_wc__db_t *db = eb->db;
198
199  if (! status->versioned)
200    return SVN_NO_ERROR; /* unversioned (includes dir externals) */
201
202  if (status->node_status == svn_wc_status_conflicted
203      && status->text_status == svn_wc_status_none
204      && status->prop_status == svn_wc_status_none)
205    {
206      /* Node is an actual only node describing a tree conflict */
207      return SVN_NO_ERROR;
208    }
209
210  /* Not text/prop modified, not copied. Easy out */
211  if (status->node_status == svn_wc_status_normal && !status->copied)
212    return SVN_NO_ERROR;
213
214  /* Mark all directories where we are no longer inside as closed */
215  while (eb->cur
216         && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath))
217    {
218      struct node_state_t *ns = eb->cur;
219
220      if (!ns->skip)
221        {
222          if (ns->propchanges)
223            SVN_ERR(eb->processor->dir_changed(ns->relpath,
224                                               ns->left_src,
225                                               ns->right_src,
226                                               ns->left_props,
227                                               ns->right_props,
228                                               ns->propchanges,
229                                               ns->baton,
230                                               eb->processor,
231                                               ns->pool));
232          else
233            SVN_ERR(eb->processor->dir_closed(ns->relpath,
234                                              ns->left_src,
235                                              ns->right_src,
236                                              ns->baton,
237                                              eb->processor,
238                                              ns->pool));
239        }
240      eb->cur = ns->parent;
241      svn_pool_clear(ns->pool);
242    }
243  SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool),
244                       FALSE, scratch_pool));
245
246  if (eb->cur && eb->cur->skip_children)
247    return SVN_NO_ERROR;
248
249  /* This code does about the same thing as the inner body of
250     walk_local_nodes_diff() in diff_editor.c, except that
251     it is already filtered by the status walker, doesn't have to
252     account for remote changes (and many tiny other details) */
253
254  {
255    svn_boolean_t repos_only;
256    svn_boolean_t local_only;
257    svn_wc__db_status_t db_status;
258    svn_boolean_t have_base;
259    svn_node_kind_t base_kind;
260    svn_node_kind_t db_kind = status->kind;
261    svn_depth_t depth_below_here = svn_depth_unknown;
262
263    const char *child_abspath = local_abspath;
264    const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath,
265                                                         local_abspath);
266
267
268    repos_only = FALSE;
269    local_only = FALSE;
270
271    /* ### optimize away this call using status info. Should
272           be possible in almost every case (except conflict, missing, obst.)*/
273    SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL,
274                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
275                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
276                                 NULL, NULL, NULL, NULL,
277                                 &have_base, NULL, NULL,
278                                 eb->db, local_abspath,
279                                 scratch_pool, scratch_pool));
280    if (!have_base)
281      {
282        local_only = TRUE; /* Only report additions */
283      }
284    else if (db_status == svn_wc__db_status_normal)
285      {
286        /* Simple diff */
287        base_kind = db_kind;
288      }
289    else if (db_status == svn_wc__db_status_deleted)
290      {
291        svn_wc__db_status_t base_status;
292        repos_only = TRUE;
293        SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
294                                         NULL, NULL, NULL, NULL, NULL,
295                                         NULL, NULL, NULL, NULL, NULL,
296                                         NULL, NULL, NULL,
297                                         eb->db, local_abspath,
298                                         scratch_pool, scratch_pool));
299
300        if (base_status != svn_wc__db_status_normal)
301          return SVN_NO_ERROR;
302      }
303    else
304      {
305        /* working status is either added or deleted */
306        svn_wc__db_status_t base_status;
307
308        SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
309                                         NULL, NULL, NULL, NULL, NULL,
310                                         NULL, NULL, NULL, NULL, NULL,
311                                         NULL, NULL, NULL,
312                                         eb->db, local_abspath,
313                                         scratch_pool, scratch_pool));
314
315        if (base_status != svn_wc__db_status_normal)
316          local_only = TRUE;
317        else if (base_kind != db_kind || !eb->ignore_ancestry)
318          {
319            repos_only = TRUE;
320            local_only = TRUE;
321          }
322      }
323
324    if (repos_only)
325      {
326        /* Report repository form deleted */
327        if (base_kind == svn_node_file)
328          SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
329                                              child_relpath,
330                                              SVN_INVALID_REVNUM,
331                                              eb->processor,
332                                              eb->cur ? eb->cur->baton : NULL,
333                                              scratch_pool));
334        else if (base_kind == svn_node_dir)
335          SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
336                                             child_relpath,
337                                             SVN_INVALID_REVNUM,
338                                             depth_below_here,
339                                             eb->processor,
340                                             eb->cur ? eb->cur->baton : NULL,
341                                             eb->cancel_func,
342                                             eb->cancel_baton,
343                                             scratch_pool));
344      }
345    else if (!local_only)
346      {
347        /* Diff base against actual */
348        if (db_kind == svn_node_file)
349          {
350            SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath,
351                                                   child_relpath,
352                                                   SVN_INVALID_REVNUM,
353                                                   eb->processor,
354                                                   eb->cur
355                                                        ? eb->cur->baton
356                                                        : NULL,
357                                                   FALSE,
358                                                   eb->cancel_func,
359                                                   eb->cancel_baton,
360                                                   scratch_pool));
361          }
362        else if (db_kind == svn_node_dir)
363          {
364            SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool));
365
366            if (status->prop_status != svn_wc_status_none
367                && status->prop_status != svn_wc_status_normal)
368              {
369                apr_array_header_t *propchanges;
370                SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props,
371                                                  eb->db, local_abspath,
372                                                  eb->cur->pool,
373                                                  scratch_pool));
374                SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props,
375                                              eb->db, local_abspath,
376                                              eb->cur->pool,
377                                              scratch_pool));
378
379                SVN_ERR(svn_prop_diffs(&propchanges,
380                                       eb->cur->right_props,
381                                       eb->cur->left_props,
382                                       eb->cur->pool));
383
384                eb->cur->propchanges = propchanges;
385              }
386          }
387      }
388
389    if (local_only && (db_status != svn_wc__db_status_deleted))
390      {
391        if (db_kind == svn_node_file)
392          SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
393                                               child_relpath,
394                                               eb->processor,
395                                               eb->cur ? eb->cur->baton : NULL,
396                                               FALSE,
397                                               eb->cancel_func,
398                                               eb->cancel_baton,
399                                               scratch_pool));
400        else if (db_kind == svn_node_dir)
401          SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
402                                              child_relpath, depth_below_here,
403                                              eb->processor,
404                                              eb->cur ? eb->cur->baton : NULL,
405                                              FALSE,
406                                              eb->cancel_func,
407                                              eb->cancel_baton,
408                                              scratch_pool));
409      }
410
411    if (db_kind == svn_node_dir && (local_only || repos_only))
412      SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool));
413  }
414
415  return SVN_NO_ERROR;
416}
417
418
419/* Public Interface */
420svn_error_t *
421svn_wc__diff7(const char **root_relpath,
422              svn_boolean_t *root_is_dir,
423              svn_wc_context_t *wc_ctx,
424              const char *local_abspath,
425              svn_depth_t depth,
426              svn_boolean_t ignore_ancestry,
427              const apr_array_header_t *changelist_filter,
428              const svn_diff_tree_processor_t *diff_processor,
429              svn_cancel_func_t cancel_func,
430              void *cancel_baton,
431              apr_pool_t *result_pool,
432              apr_pool_t *scratch_pool)
433{
434  struct diff_baton eb = { 0 };
435  svn_node_kind_t kind;
436  svn_boolean_t get_all;
437
438  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
439  SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath,
440                               FALSE /* allow_missing */,
441                               TRUE /* show_deleted */,
442                               FALSE /* show_hidden */,
443                               scratch_pool));
444
445  eb.anchor_abspath = local_abspath;
446
447  if (root_relpath)
448    {
449      svn_boolean_t is_wcroot;
450
451      SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot,
452                                   wc_ctx->db, local_abspath, scratch_pool));
453
454      if (!is_wcroot)
455        eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
456    }
457  else if (kind != svn_node_dir)
458    eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
459
460  if (root_relpath)
461    *root_relpath = apr_pstrdup(result_pool,
462                                svn_dirent_skip_ancestor(eb.anchor_abspath,
463                                                         local_abspath));
464  if (root_is_dir)
465    *root_is_dir = (kind == svn_node_dir);
466
467  /* Apply changelist filtering to the output */
468  if (changelist_filter && changelist_filter->nelts)
469    {
470      apr_hash_t *changelist_hash;
471
472      SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
473                                         result_pool));
474      diff_processor = svn_wc__changelist_filter_tree_processor_create(
475                         diff_processor, wc_ctx, local_abspath,
476                         changelist_hash, result_pool);
477    }
478
479  eb.db = wc_ctx->db;
480  eb.processor = diff_processor;
481  eb.ignore_ancestry = ignore_ancestry;
482  eb.pool = scratch_pool;
483
484  if (ignore_ancestry)
485    get_all = TRUE; /* We need unmodified descendants of copies */
486  else
487    get_all = FALSE;
488
489  /* Walk status handles files and directories */
490  SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth,
491                                       get_all,
492                                       TRUE /* no_ignore */,
493                                       FALSE /* ignore_text_mods */,
494                                       NULL /* ignore_patterns */,
495                                       diff_status_callback, &eb,
496                                       cancel_func, cancel_baton,
497                                       scratch_pool));
498
499  /* Close the remaining open directories */
500  while (eb.cur)
501    {
502      struct node_state_t *ns = eb.cur;
503
504      if (!ns->skip)
505        {
506          if (ns->propchanges)
507            SVN_ERR(diff_processor->dir_changed(ns->relpath,
508                                                ns->left_src,
509                                                ns->right_src,
510                                                ns->left_props,
511                                                ns->right_props,
512                                                ns->propchanges,
513                                                ns->baton,
514                                                diff_processor,
515                                                ns->pool));
516          else
517            SVN_ERR(diff_processor->dir_closed(ns->relpath,
518                                               ns->left_src,
519                                               ns->right_src,
520                                               ns->baton,
521                                               diff_processor,
522                                               ns->pool));
523        }
524      eb.cur = ns->parent;
525      svn_pool_clear(ns->pool);
526    }
527
528  return SVN_NO_ERROR;
529}
530
531svn_error_t *
532svn_wc_diff6(svn_wc_context_t *wc_ctx,
533             const char *local_abspath,
534             const svn_wc_diff_callbacks4_t *callbacks,
535             void *callback_baton,
536             svn_depth_t depth,
537             svn_boolean_t ignore_ancestry,
538             svn_boolean_t show_copies_as_adds,
539             svn_boolean_t use_git_diff_format,
540             const apr_array_header_t *changelist_filter,
541             svn_cancel_func_t cancel_func,
542             void *cancel_baton,
543             apr_pool_t *scratch_pool)
544{
545  const svn_diff_tree_processor_t *processor;
546
547  SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
548                                      callbacks, callback_baton, TRUE,
549                                      scratch_pool, scratch_pool));
550
551  if (use_git_diff_format)
552    show_copies_as_adds = TRUE;
553  if (show_copies_as_adds)
554    ignore_ancestry = FALSE;
555
556  if (! show_copies_as_adds && !use_git_diff_format)
557    processor = svn_diff__tree_processor_copy_as_changed_create(processor,
558                                                                scratch_pool);
559
560  return svn_error_trace(svn_wc__diff7(NULL, NULL,
561                                       wc_ctx, local_abspath,
562                                       depth,
563                                       ignore_ancestry,
564                                       changelist_filter,
565                                       processor,
566                                       cancel_func, cancel_baton,
567                                       scratch_pool, scratch_pool));
568}
569
570