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 */
54 struct 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 */
77 struct 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  */
103 static svn_error_t *
ensure_state(struct diff_baton * eb,const char * local_abspath,svn_boolean_t recursive_skip,apr_pool_t * scratch_pool)104 ensure_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 */
190 static svn_error_t *
diff_status_callback(void * baton,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)191 diff_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              || db_status == svn_wc__db_status_incomplete)
286       {
287         /* Simple diff */
288         base_kind = db_kind;
289       }
290     else if (db_status == svn_wc__db_status_deleted)
291       {
292         svn_wc__db_status_t base_status;
293         repos_only = TRUE;
294         SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
295                                          NULL, NULL, NULL, NULL, NULL,
296                                          NULL, NULL, NULL, NULL, NULL,
297                                          NULL, NULL, NULL,
298                                          eb->db, local_abspath,
299                                          scratch_pool, scratch_pool));
300 
301         if (base_status != svn_wc__db_status_normal
302             && base_status != svn_wc__db_status_incomplete)
303           return SVN_NO_ERROR;
304       }
305     else
306       {
307         /* working status is either added or deleted */
308         svn_wc__db_status_t base_status;
309 
310         SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
311                                          NULL, NULL, NULL, NULL, NULL,
312                                          NULL, NULL, NULL, NULL, NULL,
313                                          NULL, NULL, NULL,
314                                          eb->db, local_abspath,
315                                          scratch_pool, scratch_pool));
316 
317         if (base_status != svn_wc__db_status_normal
318             && base_status != svn_wc__db_status_incomplete)
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         /* Moved from. Relative from diff anchor*/
395         const char *moved_from_relpath = NULL;
396 
397         if (status->moved_from_abspath)
398           {
399             moved_from_relpath = svn_dirent_skip_ancestor(
400                                           eb->anchor_abspath,
401                                           status->moved_from_abspath);
402           }
403 
404         if (db_kind == svn_node_file)
405           SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
406                                                child_relpath,
407                                                moved_from_relpath,
408                                                eb->processor,
409                                                eb->cur ? eb->cur->baton : NULL,
410                                                FALSE,
411                                                eb->cancel_func,
412                                                eb->cancel_baton,
413                                                scratch_pool));
414         else if (db_kind == svn_node_dir)
415           SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
416                                               child_relpath, depth_below_here,
417                                               moved_from_relpath,
418                                               eb->processor,
419                                               eb->cur ? eb->cur->baton : NULL,
420                                               FALSE,
421                                               eb->cancel_func,
422                                               eb->cancel_baton,
423                                               scratch_pool));
424       }
425 
426     if (db_kind == svn_node_dir && (local_only || repos_only))
427       SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool));
428   }
429 
430   return SVN_NO_ERROR;
431 }
432 
433 
434 /* Public Interface */
435 svn_error_t *
svn_wc__diff7(svn_boolean_t anchor_at_given_paths,svn_wc_context_t * wc_ctx,const char * local_abspath,svn_depth_t depth,svn_boolean_t ignore_ancestry,const apr_array_header_t * changelist_filter,const svn_diff_tree_processor_t * diff_processor,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)436 svn_wc__diff7(svn_boolean_t anchor_at_given_paths,
437               svn_wc_context_t *wc_ctx,
438               const char *local_abspath,
439               svn_depth_t depth,
440               svn_boolean_t ignore_ancestry,
441               const apr_array_header_t *changelist_filter,
442               const svn_diff_tree_processor_t *diff_processor,
443               svn_cancel_func_t cancel_func,
444               void *cancel_baton,
445               apr_pool_t *result_pool,
446               apr_pool_t *scratch_pool)
447 {
448   struct diff_baton eb = { 0 };
449   svn_node_kind_t kind;
450   svn_boolean_t get_all;
451 
452   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
453   SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath,
454                                FALSE /* allow_missing */,
455                                TRUE /* show_deleted */,
456                                FALSE /* show_hidden */,
457                                scratch_pool));
458 
459   eb.anchor_abspath = local_abspath;
460 
461   if (anchor_at_given_paths)
462     {
463       /* Anchor the underlying diff processor at the parent of
464          LOCAL_ABSPATH (if possible), and adjust so the outgoing
465          DIFF_PROCESSOR is always anchored at LOCAL_ABSPATH. */
466       /* ### Why anchor the underlying diff processor at the parent? */
467       svn_boolean_t is_wcroot;
468 
469       SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot,
470                                    wc_ctx->db, local_abspath, scratch_pool));
471 
472       if (!is_wcroot)
473         {
474           const char *relpath;
475 
476           eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
477           relpath = svn_dirent_basename(local_abspath, NULL);
478           diff_processor = svn_diff__tree_processor_filter_create(
479                              diff_processor, relpath, scratch_pool);
480         }
481     }
482   else if (kind != svn_node_dir)
483     eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
484 
485   /* Apply changelist filtering to the output */
486   if (changelist_filter && changelist_filter->nelts)
487     {
488       apr_hash_t *changelist_hash;
489 
490       SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
491                                          result_pool));
492       diff_processor = svn_wc__changelist_filter_tree_processor_create(
493                          diff_processor, wc_ctx, eb.anchor_abspath,
494                          changelist_hash, result_pool);
495     }
496 
497   eb.db = wc_ctx->db;
498   eb.processor = diff_processor;
499   eb.ignore_ancestry = ignore_ancestry;
500   eb.pool = scratch_pool;
501 
502   if (ignore_ancestry)
503     get_all = TRUE; /* We need unmodified descendants of copies */
504   else
505     get_all = FALSE;
506 
507   /* Walk status handles files and directories */
508   SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth,
509                                        get_all,
510                                        TRUE /* no_ignore */,
511                                        FALSE /* ignore_text_mods */,
512                                        NULL /* ignore_patterns */,
513                                        diff_status_callback, &eb,
514                                        cancel_func, cancel_baton,
515                                        scratch_pool));
516 
517   /* Close the remaining open directories */
518   while (eb.cur)
519     {
520       struct node_state_t *ns = eb.cur;
521 
522       if (!ns->skip)
523         {
524           if (ns->propchanges)
525             SVN_ERR(diff_processor->dir_changed(ns->relpath,
526                                                 ns->left_src,
527                                                 ns->right_src,
528                                                 ns->left_props,
529                                                 ns->right_props,
530                                                 ns->propchanges,
531                                                 ns->baton,
532                                                 diff_processor,
533                                                 ns->pool));
534           else
535             SVN_ERR(diff_processor->dir_closed(ns->relpath,
536                                                ns->left_src,
537                                                ns->right_src,
538                                                ns->baton,
539                                                diff_processor,
540                                                ns->pool));
541         }
542       eb.cur = ns->parent;
543       svn_pool_clear(ns->pool);
544     }
545 
546   return SVN_NO_ERROR;
547 }
548 
549 svn_error_t *
svn_wc_diff6(svn_wc_context_t * wc_ctx,const char * local_abspath,const svn_wc_diff_callbacks4_t * callbacks,void * callback_baton,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t show_copies_as_adds,svn_boolean_t use_git_diff_format,const apr_array_header_t * changelist_filter,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)550 svn_wc_diff6(svn_wc_context_t *wc_ctx,
551              const char *local_abspath,
552              const svn_wc_diff_callbacks4_t *callbacks,
553              void *callback_baton,
554              svn_depth_t depth,
555              svn_boolean_t ignore_ancestry,
556              svn_boolean_t show_copies_as_adds,
557              svn_boolean_t use_git_diff_format,
558              const apr_array_header_t *changelist_filter,
559              svn_cancel_func_t cancel_func,
560              void *cancel_baton,
561              apr_pool_t *scratch_pool)
562 {
563   const svn_diff_tree_processor_t *processor;
564 
565   SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
566                                       callbacks, callback_baton, TRUE,
567                                       scratch_pool, scratch_pool));
568 
569   if (use_git_diff_format)
570     show_copies_as_adds = TRUE;
571   if (show_copies_as_adds)
572     ignore_ancestry = FALSE;
573 
574   if (! show_copies_as_adds && !use_git_diff_format)
575     processor = svn_diff__tree_processor_copy_as_changed_create(processor,
576                                                                 scratch_pool);
577 
578   return svn_error_trace(svn_wc__diff7(FALSE,
579                                        wc_ctx, local_abspath,
580                                        depth,
581                                        ignore_ancestry,
582                                        changelist_filter,
583                                        processor,
584                                        cancel_func, cancel_baton,
585                                        scratch_pool, scratch_pool));
586 }
587 
588