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