]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_client/repos_diff.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_client / repos_diff.c
1 /*
2  * repos_diff.c -- The diff editor for comparing two repository versions
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24 /* This code uses an editor driven by a tree delta between two
25  * repository revisions (REV1 and REV2). For each file encountered in
26  * the delta the editor constructs two temporary files, one for each
27  * revision. This necessitates a separate request for the REV1 version
28  * of the file when the delta shows the file being modified or
29  * deleted. Files that are added by the delta do not require a
30  * separate request, the REV1 version is empty and the delta is
31  * sufficient to construct the REV2 version. When both versions of
32  * each file have been created the diff callback is invoked to display
33  * the difference between the two files.  */
34
35 #include <apr_uri.h>
36 #include <apr_md5.h>
37 #include <assert.h>
38
39 #include "svn_checksum.h"
40 #include "svn_hash.h"
41 #include "svn_wc.h"
42 #include "svn_pools.h"
43 #include "svn_dirent_uri.h"
44 #include "svn_path.h"
45 #include "svn_io.h"
46 #include "svn_props.h"
47 #include "svn_private_config.h"
48
49 #include "client.h"
50
51 #include "private/svn_subr_private.h"
52 #include "private/svn_wc_private.h"
53 #include "private/svn_editor.h"
54
55 /* Overall crawler editor baton.  */
56 struct edit_baton {
57   /* The passed depth */
58   svn_depth_t depth;
59
60   /* The result processor */
61   const svn_diff_tree_processor_t *processor;
62
63   /* RA_SESSION is the open session for making requests to the RA layer */
64   svn_ra_session_t *ra_session;
65
66   /* The rev1 from the '-r Rev1:Rev2' command line option */
67   svn_revnum_t revision;
68
69   /* The rev2 from the '-r Rev1:Rev2' option, specifically set by
70      set_target_revision(). */
71   svn_revnum_t target_revision;
72
73   /* The path to a temporary empty file used for add/delete
74      differences.  The path is cached here so that it can be reused,
75      since all empty files are the same. */
76   const char *empty_file;
77
78   /* Empty hash used for adds. */
79   apr_hash_t *empty_hash;
80
81   /* Whether to report text deltas */
82   svn_boolean_t text_deltas;
83
84   /* A callback used to see if the client wishes to cancel the running
85      operation. */
86   svn_cancel_func_t cancel_func;
87
88   /* A baton to pass to the cancellation callback. */
89   void *cancel_baton;
90
91   apr_pool_t *pool;
92 };
93
94 typedef struct deleted_path_notify_t
95 {
96   svn_node_kind_t kind;
97   svn_wc_notify_action_t action;
98   svn_wc_notify_state_t state;
99   svn_boolean_t tree_conflicted;
100 } deleted_path_notify_t;
101
102 /* Directory level baton.
103  */
104 struct dir_baton {
105   /* Gets set if the directory is added rather than replaced/unchanged. */
106   svn_boolean_t added;
107
108   /* Gets set if this operation caused a tree-conflict on this directory
109    * (does not show tree-conflicts persisting from before this operation). */
110   svn_boolean_t tree_conflicted;
111
112   /* If TRUE, this node is skipped entirely.
113    * This is used to skip all children of a tree-conflicted
114    * directory without setting TREE_CONFLICTED to TRUE everywhere. */
115   svn_boolean_t skip;
116
117   /* If TRUE, all children of this directory are skipped. */
118   svn_boolean_t skip_children;
119
120   /* The path of the directory within the repository */
121   const char *path;
122
123   /* The baton for the parent directory, or null if this is the root of the
124      hierarchy to be compared. */
125   struct dir_baton *parent_baton;
126
127   /* The overall crawler editor baton. */
128   struct edit_baton *edit_baton;
129
130   /* A cache of any property changes (svn_prop_t) received for this dir. */
131   apr_array_header_t *propchanges;
132
133   /* Boolean indicating whether a node property was changed */
134   svn_boolean_t has_propchange;
135
136   /* Baton for svn_diff_tree_processor_t */
137   void *pdb;
138   svn_diff_source_t *left_source;
139   svn_diff_source_t *right_source;
140
141   /* The pool passed in by add_dir, open_dir, or open_root.
142      Also, the pool this dir baton is allocated in. */
143   apr_pool_t *pool;
144
145   /* Base revision of directory. */
146   svn_revnum_t base_revision;
147
148   /* Number of users of baton. Its pool will be destroyed 0 */
149   int users;
150 };
151
152 /* File level baton.
153  */
154 struct file_baton {
155   /* Reference to parent baton */
156   struct dir_baton *parent_baton;
157
158   /* Gets set if the file is added rather than replaced. */
159   svn_boolean_t added;
160
161   /* Gets set if this operation caused a tree-conflict on this file
162    * (does not show tree-conflicts persisting from before this operation). */
163   svn_boolean_t tree_conflicted;
164
165   /* If TRUE, this node is skipped entirely.
166    * This is currently used to skip all children of a tree-conflicted
167    * directory. */
168   svn_boolean_t skip;
169
170   /* The path of the file within the repository */
171   const char *path;
172
173   /* The path and APR file handle to the temporary file that contains the
174      first repository version.  Also, the pristine-property list of
175      this file. */
176   const char *path_start_revision;
177   apr_hash_t *pristine_props;
178   svn_revnum_t base_revision;
179
180   /* The path and APR file handle to the temporary file that contains the
181      second repository version.  These fields are set when processing
182      textdelta and file deletion, and will be NULL if there's no
183      textual difference between the two revisions. */
184   const char *path_end_revision;
185
186   /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */
187   svn_txdelta_window_handler_t apply_handler;
188   void *apply_baton;
189
190   /* The overall crawler editor baton. */
191   struct edit_baton *edit_baton;
192
193   /* Holds the checksum of the start revision file */
194   svn_checksum_t *start_md5_checksum;
195
196   /* Holds the resulting md5 digest of a textdelta transform */
197   unsigned char result_digest[APR_MD5_DIGESTSIZE];
198   svn_checksum_t *result_md5_checksum;
199
200   /* A cache of any property changes (svn_prop_t) received for this file. */
201   apr_array_header_t *propchanges;
202
203   /* Boolean indicating whether a node property was changed */
204   svn_boolean_t has_propchange;
205
206   /* Baton for svn_diff_tree_processor_t */
207   void *pfb;
208   svn_diff_source_t *left_source;
209   svn_diff_source_t *right_source;
210
211   /* The pool passed in by add_file or open_file.
212      Also, the pool this file_baton is allocated in. */
213   apr_pool_t *pool;
214 };
215
216 /* Create a new directory baton for PATH in POOL.  ADDED is set if
217  * this directory is being added rather than replaced. PARENT_BATON is
218  * the baton of the parent directory (or NULL if this is the root of
219  * the comparison hierarchy). The directory and its parent may or may
220  * not exist in the working copy.  EDIT_BATON is the overall crawler
221  * editor baton.
222  */
223 static struct dir_baton *
224 make_dir_baton(const char *path,
225                struct dir_baton *parent_baton,
226                struct edit_baton *edit_baton,
227                svn_boolean_t added,
228                svn_revnum_t base_revision,
229                apr_pool_t *result_pool)
230 {
231   apr_pool_t *dir_pool = svn_pool_create(result_pool);
232   struct dir_baton *dir_baton = apr_pcalloc(dir_pool, sizeof(*dir_baton));
233
234   dir_baton->parent_baton = parent_baton;
235   dir_baton->edit_baton = edit_baton;
236   dir_baton->added = added;
237   dir_baton->tree_conflicted = FALSE;
238   dir_baton->skip = FALSE;
239   dir_baton->skip_children = FALSE;
240   dir_baton->pool = dir_pool;
241   dir_baton->path = apr_pstrdup(dir_pool, path);
242   dir_baton->propchanges  = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
243   dir_baton->base_revision = base_revision;
244   dir_baton->users++;
245
246   if (parent_baton)
247     parent_baton->users++;
248
249   return dir_baton;
250 }
251
252 /* New function. Called by everyone who has a reference when done */
253 static svn_error_t *
254 release_dir(struct dir_baton *db)
255 {
256   assert(db->users > 0);
257
258   db->users--;
259   if (db->users)
260      return SVN_NO_ERROR;
261
262   {
263     struct dir_baton *pb = db->parent_baton;
264
265     svn_pool_destroy(db->pool);
266
267     if (pb != NULL)
268       SVN_ERR(release_dir(pb));
269   }
270
271   return SVN_NO_ERROR;
272 }
273
274 /* Create a new file baton for PATH in POOL, which is a child of
275  * directory PARENT_PATH. ADDED is set if this file is being added
276  * rather than replaced.  EDIT_BATON is a pointer to the global edit
277  * baton.
278  */
279 static struct file_baton *
280 make_file_baton(const char *path,
281                 struct dir_baton *parent_baton,
282                 svn_boolean_t added,
283                 apr_pool_t *result_pool)
284 {
285   apr_pool_t *file_pool = svn_pool_create(result_pool);
286   struct file_baton *file_baton = apr_pcalloc(file_pool, sizeof(*file_baton));
287
288   file_baton->parent_baton = parent_baton;
289   file_baton->edit_baton = parent_baton->edit_baton;
290   file_baton->added = added;
291   file_baton->tree_conflicted = FALSE;
292   file_baton->skip = FALSE;
293   file_baton->pool = file_pool;
294   file_baton->path = apr_pstrdup(file_pool, path);
295   file_baton->propchanges  = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
296   file_baton->base_revision = parent_baton->edit_baton->revision;
297
298   parent_baton->users++;
299
300   return file_baton;
301 }
302
303 /* Get revision FB->base_revision of the file described by FB from the
304  * repository, through FB->edit_baton->ra_session.
305  *
306  * Unless PROPS_ONLY is true:
307  *   Set FB->path_start_revision to the path of a new temporary file containing
308  *   the file's text.
309  *   Set FB->start_md5_checksum to that file's MD-5 checksum.
310  *   Install a pool cleanup handler on FB->pool to delete the file.
311  *
312  * Always:
313  *   Set FB->pristine_props to a new hash containing the file's properties.
314  *
315  * Allocate all results in FB->pool.
316  */
317 static svn_error_t *
318 get_file_from_ra(struct file_baton *fb,
319                  svn_boolean_t props_only,
320                  apr_pool_t *scratch_pool)
321 {
322   if (! props_only)
323     {
324       svn_stream_t *fstream;
325
326       SVN_ERR(svn_stream_open_unique(&fstream, &(fb->path_start_revision),
327                                      NULL, svn_io_file_del_on_pool_cleanup,
328                                      fb->pool, scratch_pool));
329
330       fstream = svn_stream_checksummed2(fstream, NULL, &fb->start_md5_checksum,
331                                         svn_checksum_md5, TRUE, scratch_pool);
332
333       /* Retrieve the file and its properties */
334       SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
335                               fb->path,
336                               fb->base_revision,
337                               fstream, NULL,
338                               &(fb->pristine_props),
339                               fb->pool));
340       SVN_ERR(svn_stream_close(fstream));
341     }
342   else
343     {
344       SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
345                               fb->path,
346                               fb->base_revision,
347                               NULL, NULL,
348                               &(fb->pristine_props),
349                               fb->pool));
350     }
351
352   return SVN_NO_ERROR;
353 }
354
355 /* Remove every no-op property change from CHANGES: that is, remove every
356    entry in which the target value is the same as the value of the
357    corresponding property in PRISTINE_PROPS.
358
359      Issue #3657 'dav update report handler in skelta mode can cause
360      spurious conflicts'.  When communicating with the repository via ra_serf,
361      the change_dir_prop and change_file_prop svn_delta_editor_t
362      callbacks are called (obviously) when a directory or file property has
363      changed between the start and end of the edit.  Less obvious however,
364      is that these callbacks may be made describing *all* of the properties
365      on FILE_BATON->PATH when using the DAV providers, not just the change(s).
366      (Specifically ra_serf does it for diff/merge/update/switch).
367
368      This means that the change_[file|dir]_prop svn_delta_editor_t callbacks
369      may be made where there are no property changes (i.e. a noop change of
370      NAME from VALUE to VALUE).  Normally this is harmless, but during a
371      merge it can result in spurious conflicts if the WC's pristine property
372      NAME has a value other than VALUE.  In an ideal world the mod_dav_svn
373      update report handler, when in 'skelta' mode and describing changes to
374      a path on which a property has changed, wouldn't ask the client to later
375      fetch all properties and figure out what has changed itself.  The server
376      already knows which properties have changed!
377
378      Regardless, such a change is not yet implemented, and even when it is,
379      the client should DTRT with regard to older servers which behave this
380      way.  Hence this little hack:  We populate FILE_BATON->PROPCHANGES only
381      with *actual* property changes.
382
383      See http://subversion.tigris.org/issues/show_bug.cgi?id=3657#desc9 and
384      http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details.
385  */
386 static void
387 remove_non_prop_changes(apr_hash_t *pristine_props,
388                         apr_array_header_t *changes)
389 {
390   int i;
391
392   for (i = 0; i < changes->nelts; i++)
393     {
394       svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t);
395
396       if (change->value)
397         {
398           const svn_string_t *old_val = svn_hash_gets(pristine_props,
399                                                       change->name);
400
401           if (old_val && svn_string_compare(old_val, change->value))
402             {
403               int j;
404
405               /* Remove the matching change by shifting the rest */
406               for (j = i; j < changes->nelts - 1; j++)
407                 {
408                   APR_ARRAY_IDX(changes, j, svn_prop_t)
409                        = APR_ARRAY_IDX(changes, j+1, svn_prop_t);
410                 }
411               changes->nelts--;
412             }
413         }
414     }
415 }
416
417 /* Get the empty file associated with the edit baton. This is cached so
418  * that it can be reused, all empty files are the same.
419  */
420 static svn_error_t *
421 get_empty_file(struct edit_baton *eb,
422                const char **empty_file_path)
423 {
424   /* Create the file if it does not exist */
425   /* Note that we tried to use /dev/null in r857294, but
426      that won't work on Windows: it's impossible to stat NUL */
427   if (!eb->empty_file)
428     SVN_ERR(svn_io_open_unique_file3(NULL, &(eb->empty_file), NULL,
429                                      svn_io_file_del_on_pool_cleanup,
430                                      eb->pool, eb->pool));
431
432   *empty_file_path = eb->empty_file;
433
434   return SVN_NO_ERROR;
435 }
436
437 /* An svn_delta_editor_t function.  */
438 static svn_error_t *
439 set_target_revision(void *edit_baton,
440                     svn_revnum_t target_revision,
441                     apr_pool_t *pool)
442 {
443   struct edit_baton *eb = edit_baton;
444
445   eb->target_revision = target_revision;
446   return SVN_NO_ERROR;
447 }
448
449 /* An svn_delta_editor_t function. The root of the comparison hierarchy */
450 static svn_error_t *
451 open_root(void *edit_baton,
452           svn_revnum_t base_revision,
453           apr_pool_t *pool,
454           void **root_baton)
455 {
456   struct edit_baton *eb = edit_baton;
457   struct dir_baton *db = make_dir_baton("", NULL, eb, FALSE, base_revision,
458                                         eb->pool);
459
460   db->left_source = svn_diff__source_create(eb->revision, db->pool);
461   db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
462
463   SVN_ERR(eb->processor->dir_opened(&db->pdb,
464                                     &db->skip,
465                                     &db->skip_children,
466                                     "",
467                                     db->left_source,
468                                     db->right_source,
469                                     NULL,
470                                     NULL,
471                                     eb->processor,
472                                     db->pool,
473                                     db->pool /* scratch_pool */));
474
475   *root_baton = db;
476   return SVN_NO_ERROR;
477 }
478
479 /* Compare a file being deleted against an empty file.
480  */
481 static svn_error_t *
482 diff_deleted_file(const char *path,
483                   struct dir_baton *db,
484                   apr_pool_t *scratch_pool)
485 {
486   struct edit_baton *eb = db->edit_baton;
487   struct file_baton *fb = make_file_baton(path, db, FALSE, scratch_pool);
488   svn_boolean_t skip = FALSE;
489   svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
490                                                            scratch_pool);
491
492   if (eb->cancel_func)
493     SVN_ERR(eb->cancel_func(eb->cancel_baton));
494
495   SVN_ERR(eb->processor->file_opened(&fb->pfb, &skip, path,
496                                      left_source,
497                                      NULL /* right_source */,
498                                      NULL /* copyfrom_source */,
499                                      db->pdb,
500                                      eb->processor,
501                                      scratch_pool, scratch_pool));
502
503   if (eb->cancel_func)
504     SVN_ERR(eb->cancel_func(eb->cancel_baton));
505
506   if (skip)
507     return SVN_NO_ERROR;
508
509   SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool));
510
511   SVN_ERR(eb->processor->file_deleted(fb->path,
512                                       left_source,
513                                       fb->path_start_revision,
514                                       fb->pristine_props,
515                                       fb->pfb,
516                                       eb->processor,
517                                       scratch_pool));
518
519   return SVN_NO_ERROR;
520 }
521
522 /* Recursively walk tree rooted at DIR (at EB->revision) in the repository,
523  * reporting all children as deleted.  Part of a workaround for issue 2333.
524  *
525  * DIR is a repository path relative to the URL in EB->ra_session.  EB is
526  * the overall crawler editor baton.  EB->revision must be a valid revision
527  * number, not SVN_INVALID_REVNUM.  Use EB->cancel_func (if not null) with
528  * EB->cancel_baton for cancellation.
529  */
530 /* ### TODO: Handle depth. */
531 static svn_error_t *
532 diff_deleted_dir(const char *path,
533                  struct dir_baton *pb,
534                  apr_pool_t *scratch_pool)
535 {
536   struct edit_baton *eb = pb->edit_baton;
537   struct dir_baton *db;
538   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
539   svn_boolean_t skip = FALSE;
540   svn_boolean_t skip_children = FALSE;
541   apr_hash_t *dirents = NULL;
542   apr_hash_t *left_props = NULL;
543   svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
544                                                            scratch_pool);
545   db = make_dir_baton(path, pb, pb->edit_baton, FALSE, SVN_INVALID_REVNUM,
546                       scratch_pool);
547
548   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(eb->revision));
549
550   if (eb->cancel_func)
551     SVN_ERR(eb->cancel_func(eb->cancel_baton));
552
553   SVN_ERR(eb->processor->dir_opened(&db->pdb, &skip, &skip_children,
554                                     path,
555                                     left_source,
556                                     NULL /* right_source */,
557                                     NULL /* copyfrom_source */,
558                                     pb->pdb,
559                                     eb->processor,
560                                     scratch_pool, iterpool));
561
562   if (!skip || !skip_children)
563     SVN_ERR(svn_ra_get_dir2(eb->ra_session,
564                             skip_children ? NULL : &dirents,
565                             NULL,
566                             skip ? NULL : &left_props,
567                             path,
568                             eb->revision,
569                             SVN_DIRENT_KIND,
570                             scratch_pool));
571
572   /* The "old" dir will be skipped by the repository report.  If required,
573    * crawl it recursively, diffing each file against the empty file.  This
574    * is a workaround for issue 2333 "'svn diff URL1 URL2' not reverse of
575    * 'svn diff URL2 URL1'". */
576   if (! skip_children)
577     {
578       apr_hash_index_t *hi;
579
580       for (hi = apr_hash_first(scratch_pool, dirents); hi;
581            hi = apr_hash_next(hi))
582         {
583           const char *child_path;
584           const char *name = svn__apr_hash_index_key(hi);
585           svn_dirent_t *dirent = svn__apr_hash_index_val(hi);
586
587           svn_pool_clear(iterpool);
588
589           child_path = svn_relpath_join(path, name, iterpool);
590
591           if (dirent->kind == svn_node_file)
592             {
593               SVN_ERR(diff_deleted_file(child_path, db, iterpool));
594             }
595           else if (dirent->kind == svn_node_dir)
596             {
597               SVN_ERR(diff_deleted_dir(child_path, db, iterpool));
598             }
599         }
600     }
601
602   if (! skip)
603     {
604       SVN_ERR(eb->processor->dir_deleted(path,
605                                          left_source,
606                                          left_props,
607                                          db->pdb,
608                                          eb->processor,
609                                          scratch_pool));
610     }
611
612   SVN_ERR(release_dir(db));
613
614   svn_pool_destroy(iterpool);
615   return SVN_NO_ERROR;
616 }
617
618 /* An svn_delta_editor_t function.  */
619 static svn_error_t *
620 delete_entry(const char *path,
621              svn_revnum_t base_revision,
622              void *parent_baton,
623              apr_pool_t *pool)
624 {
625   struct dir_baton *pb = parent_baton;
626   struct edit_baton *eb = pb->edit_baton;
627   svn_node_kind_t kind;
628   apr_pool_t *scratch_pool;
629
630   /* Process skips. */
631   if (pb->skip_children)
632     return SVN_NO_ERROR;
633
634   scratch_pool = svn_pool_create(eb->pool);
635
636   /* We need to know if this is a directory or a file */
637   SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind,
638                             scratch_pool));
639
640   switch (kind)
641     {
642     case svn_node_file:
643       {
644         SVN_ERR(diff_deleted_file(path, pb, scratch_pool));
645         break;
646       }
647     case svn_node_dir:
648       {
649         SVN_ERR(diff_deleted_dir(path, pb, scratch_pool));
650         break;
651       }
652     default:
653       break;
654     }
655
656   svn_pool_destroy(scratch_pool);
657
658   return SVN_NO_ERROR;
659 }
660
661 /* An svn_delta_editor_t function.  */
662 static svn_error_t *
663 add_directory(const char *path,
664               void *parent_baton,
665               const char *copyfrom_path,
666               svn_revnum_t copyfrom_revision,
667               apr_pool_t *pool,
668               void **child_baton)
669 {
670   struct dir_baton *pb = parent_baton;
671   struct edit_baton *eb = pb->edit_baton;
672   struct dir_baton *db;
673
674   /* ### TODO: support copyfrom? */
675
676   db = make_dir_baton(path, pb, eb, TRUE, SVN_INVALID_REVNUM, pb->pool);
677   *child_baton = db;
678
679   /* Skip *everything* within a newly tree-conflicted directory,
680    * and directories the children of which should be skipped. */
681   if (pb->skip_children)
682     {
683       db->skip = TRUE;
684       db->skip_children = TRUE;
685       return SVN_NO_ERROR;
686     }
687
688   db->right_source = svn_diff__source_create(eb->target_revision,
689                                              db->pool);
690
691   SVN_ERR(eb->processor->dir_opened(&db->pdb,
692                                     &db->skip,
693                                     &db->skip_children,
694                                     db->path,
695                                     NULL,
696                                     db->right_source,
697                                     NULL /* copyfrom_source */,
698                                     pb->pdb,
699                                     eb->processor,
700                                     db->pool, db->pool));
701
702   return SVN_NO_ERROR;
703 }
704
705 /* An svn_delta_editor_t function.  */
706 static svn_error_t *
707 open_directory(const char *path,
708                void *parent_baton,
709                svn_revnum_t base_revision,
710                apr_pool_t *pool,
711                void **child_baton)
712 {
713   struct dir_baton *pb = parent_baton;
714   struct edit_baton *eb = pb->edit_baton;
715   struct dir_baton *db;
716
717   db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool);
718
719   *child_baton = db;
720
721   /* Process Skips. */
722   if (pb->skip_children)
723     {
724       db->skip = TRUE;
725       db->skip_children = TRUE;
726       return SVN_NO_ERROR;
727     }
728
729   db->left_source = svn_diff__source_create(eb->revision, db->pool);
730   db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
731
732   SVN_ERR(eb->processor->dir_opened(&db->pdb,
733                                     &db->skip, &db->skip_children,
734                                     path,
735                                     db->left_source,
736                                     db->right_source,
737                                     NULL /* copyfrom */,
738                                     pb ? pb->pdb : NULL,
739                                     eb->processor,
740                                     db->pool, db->pool));
741
742   return SVN_NO_ERROR;
743 }
744
745
746 /* An svn_delta_editor_t function.  */
747 static svn_error_t *
748 add_file(const char *path,
749          void *parent_baton,
750          const char *copyfrom_path,
751          svn_revnum_t copyfrom_revision,
752          apr_pool_t *pool,
753          void **file_baton)
754 {
755   struct dir_baton *pb = parent_baton;
756   struct edit_baton *eb = pb->edit_baton;
757   struct file_baton *fb;
758
759   /* ### TODO: support copyfrom? */
760
761   fb = make_file_baton(path, pb, TRUE, pb->pool);
762   *file_baton = fb;
763
764   /* Process Skips. */
765   if (pb->skip_children)
766     {
767       fb->skip = TRUE;
768       return SVN_NO_ERROR;
769     }
770
771   fb->pristine_props = pb->edit_baton->empty_hash;
772
773   fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
774
775   SVN_ERR(eb->processor->file_opened(&fb->pfb,
776                                      &fb->skip,
777                                      path,
778                                      NULL,
779                                      fb->right_source,
780                                      NULL /* copy source */,
781                                      pb->pdb,
782                                      eb->processor,
783                                      fb->pool, fb->pool));
784
785   return SVN_NO_ERROR;
786 }
787
788 /* An svn_delta_editor_t function.  */
789 static svn_error_t *
790 open_file(const char *path,
791           void *parent_baton,
792           svn_revnum_t base_revision,
793           apr_pool_t *pool,
794           void **file_baton)
795 {
796   struct dir_baton *pb = parent_baton;
797   struct file_baton *fb;
798   struct edit_baton *eb = pb->edit_baton;
799   fb = make_file_baton(path, pb, FALSE, pb->pool);
800   *file_baton = fb;
801
802   /* Process Skips. */
803   if (pb->skip_children)
804     {
805       fb->skip = TRUE;
806       return SVN_NO_ERROR;
807     }
808
809   fb->base_revision = base_revision;
810
811   fb->left_source = svn_diff__source_create(eb->revision, fb->pool);
812   fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
813
814   SVN_ERR(eb->processor->file_opened(&fb->pfb,
815                                      &fb->skip,
816                                      path,
817                                      fb->left_source,
818                                      fb->right_source,
819                                      NULL /* copy source */,
820                                      pb->pdb,
821                                      eb->processor,
822                                      fb->pool, fb->pool));
823
824   return SVN_NO_ERROR;
825 }
826
827 /* Do the work of applying the text delta.  */
828 static svn_error_t *
829 window_handler(svn_txdelta_window_t *window,
830                void *window_baton)
831 {
832   struct file_baton *fb = window_baton;
833
834   SVN_ERR(fb->apply_handler(window, fb->apply_baton));
835
836   if (!window)
837     {
838       fb->result_md5_checksum = svn_checksum__from_digest_md5(
839                                         fb->result_digest,
840                                         fb->pool);
841     }
842
843   return SVN_NO_ERROR;
844 }
845
846 /* Implements svn_stream_lazyopen_func_t. */
847 static svn_error_t *
848 lazy_open_source(svn_stream_t **stream,
849                  void *baton,
850                  apr_pool_t *result_pool,
851                  apr_pool_t *scratch_pool)
852 {
853   struct file_baton *fb = baton;
854
855   SVN_ERR(svn_stream_open_readonly(stream, fb->path_start_revision,
856                                    result_pool, scratch_pool));
857
858   return SVN_NO_ERROR;
859 }
860
861 /* Implements svn_stream_lazyopen_func_t. */
862 static svn_error_t *
863 lazy_open_result(svn_stream_t **stream,
864                  void *baton,
865                  apr_pool_t *result_pool,
866                  apr_pool_t *scratch_pool)
867 {
868   struct file_baton *fb = baton;
869
870   SVN_ERR(svn_stream_open_unique(stream, &fb->path_end_revision, NULL,
871                                  svn_io_file_del_on_pool_cleanup,
872                                  result_pool, scratch_pool));
873
874   return SVN_NO_ERROR;
875 }
876
877 /* An svn_delta_editor_t function.  */
878 static svn_error_t *
879 apply_textdelta(void *file_baton,
880                 const char *base_md5_digest,
881                 apr_pool_t *pool,
882                 svn_txdelta_window_handler_t *handler,
883                 void **handler_baton)
884 {
885   struct file_baton *fb = file_baton;
886   svn_stream_t *src_stream;
887   svn_stream_t *result_stream;
888   apr_pool_t *scratch_pool = fb->pool;
889
890   /* Skip *everything* within a newly tree-conflicted directory. */
891   if (fb->skip)
892     {
893       *handler = svn_delta_noop_window_handler;
894       *handler_baton = NULL;
895       return SVN_NO_ERROR;
896     }
897
898   /* If we're not sending file text, then ignore any that we receive. */
899   if (! fb->edit_baton->text_deltas)
900     {
901       /* Supply valid paths to indicate there is a text change. */
902       SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_start_revision));
903       SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_end_revision));
904
905       *handler = svn_delta_noop_window_handler;
906       *handler_baton = NULL;
907
908       return SVN_NO_ERROR;
909     }
910
911   /* We need the expected pristine file, so go get it */
912   if (!fb->added)
913     SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool));
914   else
915     SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision)));
916
917   SVN_ERR_ASSERT(fb->path_start_revision != NULL);
918
919   if (base_md5_digest != NULL)
920     {
921       svn_checksum_t *base_md5_checksum;
922
923       SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5,
924                                      base_md5_digest, scratch_pool));
925
926       if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum))
927         return svn_error_trace(svn_checksum_mismatch_err(
928                                       base_md5_checksum,
929                                       fb->start_md5_checksum,
930                                       scratch_pool,
931                                       _("Base checksum mismatch for '%s'"),
932                                       fb->path));
933     }
934
935   /* Open the file to be used as the base for second revision */
936   src_stream = svn_stream_lazyopen_create(lazy_open_source, fb, TRUE,
937                                           scratch_pool);
938
939   /* Open the file that will become the second revision after applying the
940      text delta, it starts empty */
941   result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE,
942                                              scratch_pool);
943
944   svn_txdelta_apply(src_stream,
945                     result_stream,
946                     fb->result_digest,
947                     fb->path, fb->pool,
948                     &(fb->apply_handler), &(fb->apply_baton));
949
950   *handler = window_handler;
951   *handler_baton = file_baton;
952
953   return SVN_NO_ERROR;
954 }
955
956 /* An svn_delta_editor_t function.  When the file is closed we have a temporary
957  * file containing a pristine version of the repository file. This can
958  * be compared against the working copy.
959  *
960  * ### Ignore TEXT_CHECKSUM for now.  Someday we can use it to verify
961  * ### the integrity of the file being diffed.  Done efficiently, this
962  * ### would probably involve calculating the checksum as the data is
963  * ### received, storing the final checksum in the file_baton, and
964  * ### comparing against it here.
965  */
966 static svn_error_t *
967 close_file(void *file_baton,
968            const char *expected_md5_digest,
969            apr_pool_t *pool)
970 {
971   struct file_baton *fb = file_baton;
972   struct dir_baton *pb = fb->parent_baton;
973   struct edit_baton *eb = fb->edit_baton;
974   apr_pool_t *scratch_pool;
975
976   /* Skip *everything* within a newly tree-conflicted directory. */
977   if (fb->skip)
978     {
979       svn_pool_destroy(fb->pool);
980       SVN_ERR(release_dir(pb));
981       return SVN_NO_ERROR;
982     }
983
984   scratch_pool = fb->pool;
985
986   if (expected_md5_digest && eb->text_deltas)
987     {
988       svn_checksum_t *expected_md5_checksum;
989
990       SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
991                                      expected_md5_digest, scratch_pool));
992
993       if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum))
994         return svn_error_trace(svn_checksum_mismatch_err(
995                                       expected_md5_checksum,
996                                       fb->result_md5_checksum,
997                                       pool,
998                                       _("Checksum mismatch for '%s'"),
999                                       fb->path));
1000     }
1001
1002   if (fb->added || fb->path_end_revision || fb->has_propchange)
1003     {
1004       apr_hash_t *right_props;
1005
1006       if (!fb->added && !fb->pristine_props)
1007         {
1008           /* We didn't receive a text change, so we have no pristine props.
1009              Retrieve just the props now. */
1010           SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool));
1011         }
1012
1013       if (fb->pristine_props)
1014         remove_non_prop_changes(fb->pristine_props, fb->propchanges);
1015
1016       right_props = svn_prop__patch(fb->pristine_props, fb->propchanges,
1017                                     fb->pool);
1018
1019       if (fb->added)
1020         SVN_ERR(eb->processor->file_added(fb->path,
1021                                           NULL /* copyfrom_src */,
1022                                           fb->right_source,
1023                                           NULL /* copyfrom_file */,
1024                                           fb->path_end_revision,
1025                                           NULL /* copyfrom_props */,
1026                                           right_props,
1027                                           fb->pfb,
1028                                           eb->processor,
1029                                           fb->pool));
1030       else
1031         SVN_ERR(eb->processor->file_changed(fb->path,
1032                                             fb->left_source,
1033                                             fb->right_source,
1034                                             fb->path_end_revision
1035                                                     ? fb->path_start_revision
1036                                                     : NULL,
1037                                             fb->path_end_revision,
1038                                             fb->pristine_props,
1039                                             right_props,
1040                                             (fb->path_end_revision != NULL),
1041                                             fb->propchanges,
1042                                             fb->pfb,
1043                                             eb->processor,
1044                                             fb->pool));
1045     }
1046
1047   svn_pool_destroy(fb->pool); /* Destroy file and scratch pool */
1048
1049   SVN_ERR(release_dir(pb));
1050
1051   return SVN_NO_ERROR;
1052 }
1053
1054 /* Report any accumulated prop changes via the 'dir_props_changed' callback,
1055  * and then call the 'dir_closed' callback.  Notify about any deleted paths
1056  * within this directory that have not already been notified, and then about
1057  * this directory itself (unless it was added, in which case the notification
1058  * was done at that time).
1059  *
1060  * An svn_delta_editor_t function.  */
1061 static svn_error_t *
1062 close_directory(void *dir_baton,
1063                 apr_pool_t *pool)
1064 {
1065   struct dir_baton *db = dir_baton;
1066   struct edit_baton *eb = db->edit_baton;
1067   apr_pool_t *scratch_pool;
1068   apr_hash_t *pristine_props;
1069   svn_boolean_t send_changed = FALSE;
1070
1071   scratch_pool = db->pool;
1072
1073   if ((db->has_propchange || db->added) && !db->skip)
1074     {
1075       if (db->added)
1076         {
1077           pristine_props = eb->empty_hash;
1078         }
1079       else
1080         {
1081           SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props,
1082                                   db->path, db->base_revision, 0, scratch_pool));
1083         }
1084
1085       if (db->propchanges->nelts > 0)
1086         {
1087           remove_non_prop_changes(pristine_props, db->propchanges);
1088         }
1089
1090       if (db->propchanges->nelts > 0 || db->added)
1091         {
1092           apr_hash_t *right_props;
1093
1094           right_props = svn_prop__patch(pristine_props, db->propchanges,
1095                                         scratch_pool);
1096
1097           if (db->added)
1098             {
1099               SVN_ERR(eb->processor->dir_added(db->path,
1100                                            NULL /* copyfrom */,
1101                                            db->right_source,
1102                                            NULL /* copyfrom props */,
1103                                            right_props,
1104                                            db->pdb,
1105                                            eb->processor,
1106                                            db->pool));
1107             }
1108           else
1109             {
1110               SVN_ERR(eb->processor->dir_changed(db->path,
1111                                                  db->left_source,
1112                                                  db->right_source,
1113                                                  pristine_props,
1114                                                  right_props,
1115                                                  db->propchanges,
1116                                                  db->pdb,
1117                                                  eb->processor,
1118                                                  db->pool));
1119             }
1120
1121           send_changed = TRUE; /* Skip dir_closed */
1122         }
1123     }
1124
1125   if (! db->skip && !send_changed)
1126     {
1127       SVN_ERR(eb->processor->dir_closed(db->path,
1128                                         db->left_source,
1129                                         db->right_source,
1130                                         db->pdb,
1131                                         eb->processor,
1132                                         db->pool));
1133     }
1134   SVN_ERR(release_dir(db));
1135
1136   return SVN_NO_ERROR;
1137 }
1138
1139
1140 /* Record a prop change, which we will report later in close_file().
1141  *
1142  * An svn_delta_editor_t function.  */
1143 static svn_error_t *
1144 change_file_prop(void *file_baton,
1145                  const char *name,
1146                  const svn_string_t *value,
1147                  apr_pool_t *pool)
1148 {
1149   struct file_baton *fb = file_baton;
1150   svn_prop_t *propchange;
1151   svn_prop_kind_t propkind;
1152
1153   /* Skip *everything* within a newly tree-conflicted directory. */
1154   if (fb->skip)
1155     return SVN_NO_ERROR;
1156
1157   propkind = svn_property_kind2(name);
1158   if (propkind == svn_prop_wc_kind)
1159     return SVN_NO_ERROR;
1160   else if (propkind == svn_prop_regular_kind)
1161     fb->has_propchange = TRUE;
1162
1163   propchange = apr_array_push(fb->propchanges);
1164   propchange->name = apr_pstrdup(fb->pool, name);
1165   propchange->value = value ? svn_string_dup(value, fb->pool) : NULL;
1166
1167   return SVN_NO_ERROR;
1168 }
1169
1170 /* Make a note of this prop change, to be reported when the dir is closed.
1171  *
1172  * An svn_delta_editor_t function.  */
1173 static svn_error_t *
1174 change_dir_prop(void *dir_baton,
1175                 const char *name,
1176                 const svn_string_t *value,
1177                 apr_pool_t *pool)
1178 {
1179   struct dir_baton *db = dir_baton;
1180   svn_prop_t *propchange;
1181   svn_prop_kind_t propkind;
1182
1183   /* Skip *everything* within a newly tree-conflicted directory. */
1184   if (db->skip)
1185     return SVN_NO_ERROR;
1186
1187   propkind = svn_property_kind2(name);
1188   if (propkind == svn_prop_wc_kind)
1189     return SVN_NO_ERROR;
1190   else if (propkind == svn_prop_regular_kind)
1191     db->has_propchange = TRUE;
1192
1193   propchange = apr_array_push(db->propchanges);
1194   propchange->name = apr_pstrdup(db->pool, name);
1195   propchange->value = value ? svn_string_dup(value, db->pool) : NULL;
1196
1197   return SVN_NO_ERROR;
1198 }
1199
1200
1201 /* An svn_delta_editor_t function.  */
1202 static svn_error_t *
1203 close_edit(void *edit_baton,
1204            apr_pool_t *pool)
1205 {
1206   struct edit_baton *eb = edit_baton;
1207
1208   svn_pool_destroy(eb->pool);
1209
1210   return SVN_NO_ERROR;
1211 }
1212
1213 /* Notify that the node at PATH is 'missing'.
1214  * An svn_delta_editor_t function.  */
1215 static svn_error_t *
1216 absent_directory(const char *path,
1217                  void *parent_baton,
1218                  apr_pool_t *pool)
1219 {
1220   struct dir_baton *pb = parent_baton;
1221   struct edit_baton *eb = pb->edit_baton;
1222
1223   SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1224
1225   return SVN_NO_ERROR;
1226 }
1227
1228
1229 /* Notify that the node at PATH is 'missing'.
1230  * An svn_delta_editor_t function.  */
1231 static svn_error_t *
1232 absent_file(const char *path,
1233             void *parent_baton,
1234             apr_pool_t *pool)
1235 {
1236   struct dir_baton *pb = parent_baton;
1237   struct edit_baton *eb = pb->edit_baton;
1238
1239   SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1240
1241   return SVN_NO_ERROR;
1242 }
1243
1244 static svn_error_t *
1245 fetch_kind_func(svn_node_kind_t *kind,
1246                 void *baton,
1247                 const char *path,
1248                 svn_revnum_t base_revision,
1249                 apr_pool_t *scratch_pool)
1250 {
1251   struct edit_baton *eb = baton;
1252
1253   if (!SVN_IS_VALID_REVNUM(base_revision))
1254     base_revision = eb->revision;
1255
1256   SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1257                             scratch_pool));
1258
1259   return SVN_NO_ERROR;
1260 }
1261
1262 static svn_error_t *
1263 fetch_props_func(apr_hash_t **props,
1264                  void *baton,
1265                  const char *path,
1266                  svn_revnum_t base_revision,
1267                  apr_pool_t *result_pool,
1268                  apr_pool_t *scratch_pool)
1269 {
1270   struct edit_baton *eb = baton;
1271   svn_node_kind_t node_kind;
1272
1273   if (!SVN_IS_VALID_REVNUM(base_revision))
1274     base_revision = eb->revision;
1275
1276   SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1277                             scratch_pool));
1278
1279   if (node_kind == svn_node_file)
1280     {
1281       SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1282                               NULL, NULL, props, result_pool));
1283     }
1284   else if (node_kind == svn_node_dir)
1285     {
1286       apr_array_header_t *tmp_props;
1287
1288       SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1289                               base_revision, 0 /* Dirent fields */,
1290                               result_pool));
1291       tmp_props = svn_prop_hash_to_array(*props, result_pool);
1292       SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1293                                    result_pool));
1294       *props = svn_prop_array_to_hash(tmp_props, result_pool);
1295     }
1296   else
1297     {
1298       *props = apr_hash_make(result_pool);
1299     }
1300
1301   return SVN_NO_ERROR;
1302 }
1303
1304 static svn_error_t *
1305 fetch_base_func(const char **filename,
1306                 void *baton,
1307                 const char *path,
1308                 svn_revnum_t base_revision,
1309                 apr_pool_t *result_pool,
1310                 apr_pool_t *scratch_pool)
1311 {
1312   struct edit_baton *eb = baton;
1313   svn_stream_t *fstream;
1314   svn_error_t *err;
1315
1316   if (!SVN_IS_VALID_REVNUM(base_revision))
1317     base_revision = eb->revision;
1318
1319   SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1320                                  svn_io_file_del_on_pool_cleanup,
1321                                  result_pool, scratch_pool));
1322
1323   err = svn_ra_get_file(eb->ra_session, path, base_revision,
1324                         fstream, NULL, NULL, scratch_pool);
1325   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1326     {
1327       svn_error_clear(err);
1328       SVN_ERR(svn_stream_close(fstream));
1329
1330       *filename = NULL;
1331       return SVN_NO_ERROR;
1332     }
1333   else if (err)
1334     return svn_error_trace(err);
1335
1336   SVN_ERR(svn_stream_close(fstream));
1337
1338   return SVN_NO_ERROR;
1339 }
1340
1341 /* Create a repository diff editor and baton.  */
1342 svn_error_t *
1343 svn_client__get_diff_editor2(const svn_delta_editor_t **editor,
1344                              void **edit_baton,
1345                              svn_ra_session_t *ra_session,
1346                              svn_depth_t depth,
1347                              svn_revnum_t revision,
1348                              svn_boolean_t text_deltas,
1349                              const svn_diff_tree_processor_t *processor,
1350                              svn_cancel_func_t cancel_func,
1351                              void *cancel_baton,
1352                              apr_pool_t *result_pool)
1353 {
1354   apr_pool_t *editor_pool = svn_pool_create(result_pool);
1355   svn_delta_editor_t *tree_editor = svn_delta_default_editor(editor_pool);
1356   struct edit_baton *eb = apr_pcalloc(editor_pool, sizeof(*eb));
1357   svn_delta_shim_callbacks_t *shim_callbacks =
1358                                 svn_delta_shim_callbacks_default(editor_pool);
1359
1360   eb->pool = editor_pool;
1361   eb->depth = depth;
1362
1363   eb->processor = processor;
1364
1365   eb->ra_session = ra_session;
1366
1367   eb->revision = revision;
1368   eb->empty_file = NULL;
1369   eb->empty_hash = apr_hash_make(eb->pool);
1370   eb->text_deltas = text_deltas;
1371   eb->cancel_func = cancel_func;
1372   eb->cancel_baton = cancel_baton;
1373
1374   tree_editor->set_target_revision = set_target_revision;
1375   tree_editor->open_root = open_root;
1376   tree_editor->delete_entry = delete_entry;
1377   tree_editor->add_directory = add_directory;
1378   tree_editor->open_directory = open_directory;
1379   tree_editor->add_file = add_file;
1380   tree_editor->open_file = open_file;
1381   tree_editor->apply_textdelta = apply_textdelta;
1382   tree_editor->close_file = close_file;
1383   tree_editor->close_directory = close_directory;
1384   tree_editor->change_file_prop = change_file_prop;
1385   tree_editor->change_dir_prop = change_dir_prop;
1386   tree_editor->close_edit = close_edit;
1387   tree_editor->absent_directory = absent_directory;
1388   tree_editor->absent_file = absent_file;
1389
1390   SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1391                                             tree_editor, eb,
1392                                             editor, edit_baton,
1393                                             eb->pool));
1394
1395   shim_callbacks->fetch_kind_func = fetch_kind_func;
1396   shim_callbacks->fetch_props_func = fetch_props_func;
1397   shim_callbacks->fetch_base_func = fetch_base_func;
1398   shim_callbacks->fetch_baton = eb;
1399
1400   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1401                                    NULL, NULL, shim_callbacks,
1402                                    result_pool, result_pool));
1403
1404   return SVN_NO_ERROR;
1405 }