1/*
2 * diff_pristine.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 "props.h"
45#include "translate.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  /* Should this diff not compare copied files with their source? */
93  svn_boolean_t show_copies_as_adds;
94
95  /* Cancel function/baton */
96  svn_cancel_func_t cancel_func;
97  void *cancel_baton;
98
99  apr_pool_t *pool;
100};
101
102/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH
103   is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself,
104   but create it marked with skip+skip_children.
105 */
106static svn_error_t *
107ensure_state(struct diff_baton *eb,
108             const char *local_abspath,
109             svn_boolean_t recursive_skip,
110             apr_pool_t *scratch_pool)
111{
112  struct node_state_t *ns;
113  apr_pool_t *ns_pool;
114  if (!eb->cur)
115    {
116      const char *relpath;
117
118      relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath);
119      if (! relpath)
120        return SVN_NO_ERROR;
121
122      /* Don't recurse on the anchor, as that might loop infinately because
123            svn_dirent_dirname("/",...)   -> "/"
124            svn_dirent_dirname("C:/",...) -> "C:/" (Windows) */
125      if (*relpath)
126        SVN_ERR(ensure_state(eb,
127                             svn_dirent_dirname(local_abspath,scratch_pool),
128                             FALSE,
129                             scratch_pool));
130    }
131  else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL))
132    SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool),
133                         FALSE,
134                         scratch_pool));
135  else
136    return SVN_NO_ERROR;
137
138  if (eb->cur && eb->cur->skip_children)
139    return SVN_NO_ERROR;
140
141  ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool);
142  ns = apr_pcalloc(ns_pool, sizeof(*ns));
143
144  ns->pool = ns_pool;
145  ns->local_abspath = apr_pstrdup(ns_pool, local_abspath);
146  ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath);
147  ns->parent = eb->cur;
148  eb->cur = ns;
149
150  if (recursive_skip)
151    {
152      ns->skip = TRUE;
153      ns->skip_children = TRUE;
154      return SVN_NO_ERROR;
155    }
156
157  {
158    svn_revnum_t revision;
159    svn_error_t *err;
160
161    err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL,
162                                   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
163                                   NULL, NULL, NULL,
164                                   eb->db, local_abspath,
165                                   scratch_pool, scratch_pool);
166
167    if (err)
168      {
169        if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
170          return svn_error_trace(err);
171        svn_error_clear(err);
172
173        revision = 0; /* Use original revision? */
174      }
175    ns->left_src = svn_diff__source_create(revision, ns->pool);
176    ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool);
177
178    SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip,
179                                      &ns->skip_children,
180                                      ns->relpath,
181                                      ns->left_src,
182                                      ns->right_src,
183                                      NULL /* copyfrom_source */,
184                                      ns->parent ? ns->parent->baton : NULL,
185                                      eb->processor,
186                                      ns->pool, scratch_pool));
187  }
188
189  return SVN_NO_ERROR;
190}
191
192/* Implements svn_wc_status_func3_t */
193static svn_error_t *
194diff_status_callback(void *baton,
195                     const char *local_abspath,
196                     const svn_wc_status3_t *status,
197                     apr_pool_t *scratch_pool)
198{
199  struct diff_baton *eb = baton;
200  svn_wc__db_t *db = eb->db;
201
202  if (! status->versioned)
203    return SVN_NO_ERROR; /* unversioned (includes dir externals) */
204
205  if (status->node_status == svn_wc_status_conflicted
206      && status->text_status == svn_wc_status_none
207      && status->prop_status == svn_wc_status_none)
208    {
209      /* Node is an actual only node describing a tree conflict */
210      return SVN_NO_ERROR;
211    }
212
213  /* Not text/prop modified, not copied. Easy out */
214  if (status->node_status == svn_wc_status_normal && !status->copied)
215    return SVN_NO_ERROR;
216
217  /* Mark all directories where we are no longer inside as closed */
218  while (eb->cur
219         && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath))
220    {
221      struct node_state_t *ns = eb->cur;
222
223      if (!ns->skip)
224        {
225          if (ns->propchanges)
226            SVN_ERR(eb->processor->dir_changed(ns->relpath,
227                                               ns->left_src,
228                                               ns->right_src,
229                                               ns->left_props,
230                                               ns->right_props,
231                                               ns->propchanges,
232                                               ns->baton,
233                                               eb->processor,
234                                               ns->pool));
235          else
236            SVN_ERR(eb->processor->dir_closed(ns->relpath,
237                                              ns->left_src,
238                                              ns->right_src,
239                                              ns->baton,
240                                              eb->processor,
241                                              ns->pool));
242        }
243      eb->cur = ns->parent;
244      svn_pool_clear(ns->pool);
245    }
246  SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool),
247                       FALSE, scratch_pool));
248
249  if (eb->cur && eb->cur->skip_children)
250    return SVN_NO_ERROR;
251
252  /* This code does about the same thing as the inner body of
253     walk_local_nodes_diff() in diff_editor.c, except that
254     it is already filtered by the status walker, doesn't have to
255     account for remote changes (and many tiny other details) */
256
257  {
258    svn_boolean_t repos_only;
259    svn_boolean_t local_only;
260    svn_wc__db_status_t db_status;
261    svn_boolean_t have_base;
262    svn_node_kind_t base_kind;
263    svn_node_kind_t db_kind = status->kind;
264    svn_depth_t depth_below_here = svn_depth_unknown;
265
266    const char *child_abspath = local_abspath;
267    const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath,
268                                                         local_abspath);
269
270
271    repos_only = FALSE;
272    local_only = FALSE;
273
274    /* ### optimize away this call using status info. Should
275           be possible in almost every case (except conflict, missing, obst.)*/
276    SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL,
277                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
278                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
279                                 NULL, NULL, NULL, NULL,
280                                 &have_base, NULL, NULL,
281                                 eb->db, local_abspath,
282                                 scratch_pool, scratch_pool));
283    if (!have_base)
284      {
285        local_only = TRUE; /* Only report additions */
286      }
287    else if (db_status == svn_wc__db_status_normal)
288      {
289        /* Simple diff */
290        base_kind = db_kind;
291      }
292    else if (db_status == svn_wc__db_status_deleted)
293      {
294        svn_wc__db_status_t base_status;
295        repos_only = TRUE;
296        SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
297                                         NULL, NULL, NULL, NULL, NULL,
298                                         NULL, NULL, NULL, NULL, NULL,
299                                         NULL, NULL, NULL,
300                                         eb->db, local_abspath,
301                                         scratch_pool, scratch_pool));
302
303        if (base_status != svn_wc__db_status_normal)
304          return SVN_NO_ERROR;
305      }
306    else
307      {
308        /* working status is either added or deleted */
309        svn_wc__db_status_t base_status;
310
311        SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
312                                         NULL, NULL, NULL, NULL, NULL,
313                                         NULL, NULL, NULL, NULL, NULL,
314                                         NULL, NULL, NULL,
315                                         eb->db, local_abspath,
316                                         scratch_pool, scratch_pool));
317
318        if (base_status != svn_wc__db_status_normal)
319          local_only = TRUE;
320        else if (base_kind != db_kind || !eb->ignore_ancestry)
321          {
322            repos_only = TRUE;
323            local_only = TRUE;
324          }
325      }
326
327    if (repos_only)
328      {
329        /* Report repository form deleted */
330        if (base_kind == svn_node_file)
331          SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
332                                              child_relpath,
333                                              SVN_INVALID_REVNUM,
334                                              eb->processor,
335                                              eb->cur ? eb->cur->baton : NULL,
336                                              scratch_pool));
337        else if (base_kind == svn_node_dir)
338          SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
339                                             child_relpath,
340                                             SVN_INVALID_REVNUM,
341                                             depth_below_here,
342                                             eb->processor,
343                                             eb->cur ? eb->cur->baton : NULL,
344                                             eb->cancel_func,
345                                             eb->cancel_baton,
346                                             scratch_pool));
347      }
348    else if (!local_only)
349      {
350        /* Diff base against actual */
351        if (db_kind == svn_node_file)
352          {
353            SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath,
354                                                   child_relpath,
355                                                   SVN_INVALID_REVNUM,
356                                                   eb->processor,
357                                                   eb->cur
358                                                        ? eb->cur->baton
359                                                        : NULL,
360                                                   FALSE,
361                                                   eb->cancel_func,
362                                                   eb->cancel_baton,
363                                                   scratch_pool));
364          }
365        else if (db_kind == svn_node_dir)
366          {
367            SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool));
368
369            if (status->prop_status != svn_wc_status_none
370                && status->prop_status != svn_wc_status_normal)
371              {
372                apr_array_header_t *propchanges;
373                SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props,
374                                                  eb->db, local_abspath,
375                                                  eb->cur->pool,
376                                                  scratch_pool));
377                SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props,
378                                              eb->db, local_abspath,
379                                              eb->cur->pool,
380                                              scratch_pool));
381
382                SVN_ERR(svn_prop_diffs(&propchanges,
383                                       eb->cur->right_props,
384                                       eb->cur->left_props,
385                                       eb->cur->pool));
386
387                eb->cur->propchanges = propchanges;
388              }
389          }
390      }
391
392    if (local_only && (db_status != svn_wc__db_status_deleted))
393      {
394        if (db_kind == svn_node_file)
395          SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
396                                               child_relpath,
397                                               eb->processor,
398                                               eb->cur ? eb->cur->baton : NULL,
399                                               FALSE,
400                                               eb->cancel_func,
401                                               eb->cancel_baton,
402                                               scratch_pool));
403        else if (db_kind == svn_node_dir)
404          SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
405                                              child_relpath, depth_below_here,
406                                              eb->processor,
407                                              eb->cur ? eb->cur->baton : NULL,
408                                              FALSE,
409                                              eb->cancel_func,
410                                              eb->cancel_baton,
411                                              scratch_pool));
412      }
413
414    if (db_kind == svn_node_dir && (local_only || repos_only))
415      SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool));
416  }
417
418  return SVN_NO_ERROR;
419}
420
421
422/* Public Interface */
423svn_error_t *
424svn_wc_diff6(svn_wc_context_t *wc_ctx,
425             const char *local_abspath,
426             const svn_wc_diff_callbacks4_t *callbacks,
427             void *callback_baton,
428             svn_depth_t depth,
429             svn_boolean_t ignore_ancestry,
430             svn_boolean_t show_copies_as_adds,
431             svn_boolean_t use_git_diff_format,
432             const apr_array_header_t *changelist_filter,
433             svn_cancel_func_t cancel_func,
434             void *cancel_baton,
435             apr_pool_t *scratch_pool)
436{
437  struct diff_baton eb = { 0 };
438  svn_node_kind_t kind;
439  svn_boolean_t get_all;
440  const svn_diff_tree_processor_t *processor;
441
442  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
443  SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath,
444                               FALSE /* allow_missing */,
445                               TRUE /* show_deleted */,
446                               FALSE /* show_hidden */,
447                               scratch_pool));
448
449  if (kind == svn_node_dir)
450    eb.anchor_abspath = local_abspath;
451  else
452    eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
453
454  SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
455                                      callbacks, callback_baton, TRUE,
456                                      scratch_pool, scratch_pool));
457
458  if (use_git_diff_format)
459    show_copies_as_adds = TRUE;
460  if (show_copies_as_adds)
461    ignore_ancestry = FALSE;
462
463
464
465  /*
466  if (reverse_order)
467    processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool);
468   */
469
470  if (! show_copies_as_adds && !use_git_diff_format)
471    processor = svn_diff__tree_processor_copy_as_changed_create(processor,
472                                                                scratch_pool);
473
474  /* Apply changelist filtering to the output */
475  if (changelist_filter && changelist_filter->nelts)
476    {
477      apr_hash_t *changelist_hash;
478
479      SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
480                                         scratch_pool));
481      processor = svn_wc__changelist_filter_tree_processor_create(
482                    processor, wc_ctx, local_abspath,
483                    changelist_hash, scratch_pool);
484    }
485
486  eb.db = wc_ctx->db;
487  eb.processor = processor;
488  eb.ignore_ancestry = ignore_ancestry;
489  eb.show_copies_as_adds = show_copies_as_adds;
490  eb.pool = scratch_pool;
491
492  if (show_copies_as_adds || use_git_diff_format || !ignore_ancestry)
493    get_all = TRUE; /* We need unmodified descendants of copies */
494  else
495    get_all = FALSE;
496
497  /* Walk status handles files and directories */
498  SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth,
499                                       get_all,
500                                       TRUE /* no_ignore */,
501                                       FALSE /* ignore_text_mods */,
502                                       NULL /* ignore_patterns */,
503                                       diff_status_callback, &eb,
504                                       cancel_func, cancel_baton,
505                                       scratch_pool));
506
507  /* Close the remaining open directories */
508  while (eb.cur)
509    {
510      struct node_state_t *ns = eb.cur;
511
512      if (!ns->skip)
513        {
514          if (ns->propchanges)
515            SVN_ERR(processor->dir_changed(ns->relpath,
516                                           ns->left_src,
517                                           ns->right_src,
518                                           ns->left_props,
519                                           ns->right_props,
520                                           ns->propchanges,
521                                           ns->baton,
522                                           processor,
523                                           ns->pool));
524          else
525            SVN_ERR(processor->dir_closed(ns->relpath,
526                                          ns->left_src,
527                                          ns->right_src,
528                                          ns->baton,
529                                          processor,
530                                          ns->pool));
531        }
532      eb.cur = ns->parent;
533      svn_pool_clear(ns->pool);
534    }
535
536  return SVN_NO_ERROR;
537}
538