]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/repos_diff.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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, fb->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 added nodes, there is nothing to filter. */
393   if (apr_hash_count(pristine_props) == 0)
394     return;
395
396   for (i = 0; i < changes->nelts; i++)
397     {
398       svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t);
399
400       if (change->value)
401         {
402           const svn_string_t *old_val = svn_hash_gets(pristine_props,
403                                                       change->name);
404
405           if (old_val && svn_string_compare(old_val, change->value))
406             {
407               int j;
408
409               /* Remove the matching change by shifting the rest */
410               for (j = i; j < changes->nelts - 1; j++)
411                 {
412                   APR_ARRAY_IDX(changes, j, svn_prop_t)
413                        = APR_ARRAY_IDX(changes, j+1, svn_prop_t);
414                 }
415               changes->nelts--;
416             }
417         }
418     }
419 }
420
421 /* Get the empty file associated with the edit baton. This is cached so
422  * that it can be reused, all empty files are the same.
423  */
424 static svn_error_t *
425 get_empty_file(struct edit_baton *eb,
426                const char **empty_file_path)
427 {
428   /* Create the file if it does not exist */
429   /* Note that we tried to use /dev/null in r857294, but
430      that won't work on Windows: it's impossible to stat NUL */
431   if (!eb->empty_file)
432     SVN_ERR(svn_io_open_unique_file3(NULL, &(eb->empty_file), NULL,
433                                      svn_io_file_del_on_pool_cleanup,
434                                      eb->pool, eb->pool));
435
436   *empty_file_path = eb->empty_file;
437
438   return SVN_NO_ERROR;
439 }
440
441 /* An svn_delta_editor_t function.  */
442 static svn_error_t *
443 set_target_revision(void *edit_baton,
444                     svn_revnum_t target_revision,
445                     apr_pool_t *pool)
446 {
447   struct edit_baton *eb = edit_baton;
448
449   eb->target_revision = target_revision;
450   return SVN_NO_ERROR;
451 }
452
453 /* An svn_delta_editor_t function. The root of the comparison hierarchy */
454 static svn_error_t *
455 open_root(void *edit_baton,
456           svn_revnum_t base_revision,
457           apr_pool_t *pool,
458           void **root_baton)
459 {
460   struct edit_baton *eb = edit_baton;
461   struct dir_baton *db = make_dir_baton("", NULL, eb, FALSE, base_revision,
462                                         eb->pool);
463
464   db->left_source = svn_diff__source_create(eb->revision, db->pool);
465   db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
466
467   SVN_ERR(eb->processor->dir_opened(&db->pdb,
468                                     &db->skip,
469                                     &db->skip_children,
470                                     "",
471                                     db->left_source,
472                                     db->right_source,
473                                     NULL,
474                                     NULL,
475                                     eb->processor,
476                                     db->pool,
477                                     db->pool /* scratch_pool */));
478
479   *root_baton = db;
480   return SVN_NO_ERROR;
481 }
482
483 /* Compare a file being deleted against an empty file.
484  */
485 static svn_error_t *
486 diff_deleted_file(const char *path,
487                   struct dir_baton *db,
488                   apr_pool_t *scratch_pool)
489 {
490   struct edit_baton *eb = db->edit_baton;
491   struct file_baton *fb = make_file_baton(path, db, FALSE, scratch_pool);
492   svn_boolean_t skip = FALSE;
493   svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
494                                                            scratch_pool);
495
496   if (eb->cancel_func)
497     SVN_ERR(eb->cancel_func(eb->cancel_baton));
498
499   SVN_ERR(eb->processor->file_opened(&fb->pfb, &skip, path,
500                                      left_source,
501                                      NULL /* right_source */,
502                                      NULL /* copyfrom_source */,
503                                      db->pdb,
504                                      eb->processor,
505                                      scratch_pool, scratch_pool));
506
507   if (eb->cancel_func)
508     SVN_ERR(eb->cancel_func(eb->cancel_baton));
509
510   if (skip)
511     return SVN_NO_ERROR;
512
513   SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool));
514
515   SVN_ERR(eb->processor->file_deleted(fb->path,
516                                       left_source,
517                                       fb->path_start_revision,
518                                       fb->pristine_props,
519                                       fb->pfb,
520                                       eb->processor,
521                                       scratch_pool));
522
523   return SVN_NO_ERROR;
524 }
525
526 /* Recursively walk tree rooted at DIR (at EB->revision) in the repository,
527  * reporting all children as deleted.  Part of a workaround for issue 2333.
528  *
529  * DIR is a repository path relative to the URL in EB->ra_session.  EB is
530  * the overall crawler editor baton.  EB->revision must be a valid revision
531  * number, not SVN_INVALID_REVNUM.  Use EB->cancel_func (if not null) with
532  * EB->cancel_baton for cancellation.
533  */
534 /* ### TODO: Handle depth. */
535 static svn_error_t *
536 diff_deleted_dir(const char *path,
537                  struct dir_baton *pb,
538                  apr_pool_t *scratch_pool)
539 {
540   struct edit_baton *eb = pb->edit_baton;
541   struct dir_baton *db;
542   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
543   svn_boolean_t skip = FALSE;
544   svn_boolean_t skip_children = FALSE;
545   apr_hash_t *dirents = NULL;
546   apr_hash_t *left_props = NULL;
547   svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
548                                                            scratch_pool);
549   db = make_dir_baton(path, pb, pb->edit_baton, FALSE, SVN_INVALID_REVNUM,
550                       scratch_pool);
551
552   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(eb->revision));
553
554   if (eb->cancel_func)
555     SVN_ERR(eb->cancel_func(eb->cancel_baton));
556
557   SVN_ERR(eb->processor->dir_opened(&db->pdb, &skip, &skip_children,
558                                     path,
559                                     left_source,
560                                     NULL /* right_source */,
561                                     NULL /* copyfrom_source */,
562                                     pb->pdb,
563                                     eb->processor,
564                                     scratch_pool, iterpool));
565
566   if (!skip || !skip_children)
567     SVN_ERR(svn_ra_get_dir2(eb->ra_session,
568                             skip_children ? NULL : &dirents,
569                             NULL,
570                             skip ? NULL : &left_props,
571                             path,
572                             eb->revision,
573                             SVN_DIRENT_KIND,
574                             scratch_pool));
575
576   /* The "old" dir will be skipped by the repository report.  If required,
577    * crawl it recursively, diffing each file against the empty file.  This
578    * is a workaround for issue 2333 "'svn diff URL1 URL2' not reverse of
579    * 'svn diff URL2 URL1'". */
580   if (! skip_children)
581     {
582       apr_hash_index_t *hi;
583
584       for (hi = apr_hash_first(scratch_pool, dirents); hi;
585            hi = apr_hash_next(hi))
586         {
587           const char *child_path;
588           const char *name = apr_hash_this_key(hi);
589           svn_dirent_t *dirent = apr_hash_this_val(hi);
590
591           svn_pool_clear(iterpool);
592
593           child_path = svn_relpath_join(path, name, iterpool);
594
595           if (dirent->kind == svn_node_file)
596             {
597               SVN_ERR(diff_deleted_file(child_path, db, iterpool));
598             }
599           else if (dirent->kind == svn_node_dir)
600             {
601               SVN_ERR(diff_deleted_dir(child_path, db, iterpool));
602             }
603         }
604     }
605
606   if (! skip)
607     {
608       SVN_ERR(eb->processor->dir_deleted(path,
609                                          left_source,
610                                          left_props,
611                                          db->pdb,
612                                          eb->processor,
613                                          scratch_pool));
614     }
615
616   SVN_ERR(release_dir(db));
617
618   svn_pool_destroy(iterpool);
619   return SVN_NO_ERROR;
620 }
621
622 /* An svn_delta_editor_t function.  */
623 static svn_error_t *
624 delete_entry(const char *path,
625              svn_revnum_t base_revision,
626              void *parent_baton,
627              apr_pool_t *pool)
628 {
629   struct dir_baton *pb = parent_baton;
630   struct edit_baton *eb = pb->edit_baton;
631   svn_node_kind_t kind;
632   apr_pool_t *scratch_pool;
633
634   /* Process skips. */
635   if (pb->skip_children)
636     return SVN_NO_ERROR;
637
638   scratch_pool = svn_pool_create(eb->pool);
639
640   /* We need to know if this is a directory or a file */
641   SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind,
642                             scratch_pool));
643
644   switch (kind)
645     {
646     case svn_node_file:
647       {
648         SVN_ERR(diff_deleted_file(path, pb, scratch_pool));
649         break;
650       }
651     case svn_node_dir:
652       {
653         SVN_ERR(diff_deleted_dir(path, pb, scratch_pool));
654         break;
655       }
656     default:
657       break;
658     }
659
660   svn_pool_destroy(scratch_pool);
661
662   return SVN_NO_ERROR;
663 }
664
665 /* An svn_delta_editor_t function.  */
666 static svn_error_t *
667 add_directory(const char *path,
668               void *parent_baton,
669               const char *copyfrom_path,
670               svn_revnum_t copyfrom_revision,
671               apr_pool_t *pool,
672               void **child_baton)
673 {
674   struct dir_baton *pb = parent_baton;
675   struct edit_baton *eb = pb->edit_baton;
676   struct dir_baton *db;
677
678   /* ### TODO: support copyfrom? */
679
680   db = make_dir_baton(path, pb, eb, TRUE, SVN_INVALID_REVNUM, pb->pool);
681   *child_baton = db;
682
683   /* Skip *everything* within a newly tree-conflicted directory,
684    * and directories the children of which should be skipped. */
685   if (pb->skip_children)
686     {
687       db->skip = TRUE;
688       db->skip_children = TRUE;
689       return SVN_NO_ERROR;
690     }
691
692   db->right_source = svn_diff__source_create(eb->target_revision,
693                                              db->pool);
694
695   SVN_ERR(eb->processor->dir_opened(&db->pdb,
696                                     &db->skip,
697                                     &db->skip_children,
698                                     db->path,
699                                     NULL,
700                                     db->right_source,
701                                     NULL /* copyfrom_source */,
702                                     pb->pdb,
703                                     eb->processor,
704                                     db->pool, db->pool));
705
706   return SVN_NO_ERROR;
707 }
708
709 /* An svn_delta_editor_t function.  */
710 static svn_error_t *
711 open_directory(const char *path,
712                void *parent_baton,
713                svn_revnum_t base_revision,
714                apr_pool_t *pool,
715                void **child_baton)
716 {
717   struct dir_baton *pb = parent_baton;
718   struct edit_baton *eb = pb->edit_baton;
719   struct dir_baton *db;
720
721   db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool);
722
723   *child_baton = db;
724
725   /* Process Skips. */
726   if (pb->skip_children)
727     {
728       db->skip = TRUE;
729       db->skip_children = TRUE;
730       return SVN_NO_ERROR;
731     }
732
733   db->left_source = svn_diff__source_create(eb->revision, db->pool);
734   db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
735
736   SVN_ERR(eb->processor->dir_opened(&db->pdb,
737                                     &db->skip, &db->skip_children,
738                                     path,
739                                     db->left_source,
740                                     db->right_source,
741                                     NULL /* copyfrom */,
742                                     pb ? pb->pdb : NULL,
743                                     eb->processor,
744                                     db->pool, db->pool));
745
746   return SVN_NO_ERROR;
747 }
748
749
750 /* An svn_delta_editor_t function.  */
751 static svn_error_t *
752 add_file(const char *path,
753          void *parent_baton,
754          const char *copyfrom_path,
755          svn_revnum_t copyfrom_revision,
756          apr_pool_t *pool,
757          void **file_baton)
758 {
759   struct dir_baton *pb = parent_baton;
760   struct edit_baton *eb = pb->edit_baton;
761   struct file_baton *fb;
762
763   /* ### TODO: support copyfrom? */
764
765   fb = make_file_baton(path, pb, TRUE, pb->pool);
766   *file_baton = fb;
767
768   /* Process Skips. */
769   if (pb->skip_children)
770     {
771       fb->skip = TRUE;
772       return SVN_NO_ERROR;
773     }
774
775   fb->pristine_props = pb->edit_baton->empty_hash;
776
777   fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
778
779   SVN_ERR(eb->processor->file_opened(&fb->pfb,
780                                      &fb->skip,
781                                      path,
782                                      NULL,
783                                      fb->right_source,
784                                      NULL /* copy source */,
785                                      pb->pdb,
786                                      eb->processor,
787                                      fb->pool, fb->pool));
788
789   return SVN_NO_ERROR;
790 }
791
792 /* An svn_delta_editor_t function.  */
793 static svn_error_t *
794 open_file(const char *path,
795           void *parent_baton,
796           svn_revnum_t base_revision,
797           apr_pool_t *pool,
798           void **file_baton)
799 {
800   struct dir_baton *pb = parent_baton;
801   struct file_baton *fb;
802   struct edit_baton *eb = pb->edit_baton;
803   fb = make_file_baton(path, pb, FALSE, pb->pool);
804   *file_baton = fb;
805
806   /* Process Skips. */
807   if (pb->skip_children)
808     {
809       fb->skip = TRUE;
810       return SVN_NO_ERROR;
811     }
812
813   fb->base_revision = base_revision;
814
815   fb->left_source = svn_diff__source_create(eb->revision, fb->pool);
816   fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
817
818   SVN_ERR(eb->processor->file_opened(&fb->pfb,
819                                      &fb->skip,
820                                      path,
821                                      fb->left_source,
822                                      fb->right_source,
823                                      NULL /* copy source */,
824                                      pb->pdb,
825                                      eb->processor,
826                                      fb->pool, fb->pool));
827
828   return SVN_NO_ERROR;
829 }
830
831 /* Do the work of applying the text delta.  */
832 static svn_error_t *
833 window_handler(svn_txdelta_window_t *window,
834                void *window_baton)
835 {
836   struct file_baton *fb = window_baton;
837
838   SVN_ERR(fb->apply_handler(window, fb->apply_baton));
839
840   if (!window)
841     {
842       fb->result_md5_checksum = svn_checksum__from_digest_md5(
843                                         fb->result_digest,
844                                         fb->pool);
845     }
846
847   return SVN_NO_ERROR;
848 }
849
850 /* Implements svn_stream_lazyopen_func_t. */
851 static svn_error_t *
852 lazy_open_source(svn_stream_t **stream,
853                  void *baton,
854                  apr_pool_t *result_pool,
855                  apr_pool_t *scratch_pool)
856 {
857   struct file_baton *fb = baton;
858
859   SVN_ERR(svn_stream_open_readonly(stream, fb->path_start_revision,
860                                    result_pool, scratch_pool));
861
862   return SVN_NO_ERROR;
863 }
864
865 /* Implements svn_stream_lazyopen_func_t. */
866 static svn_error_t *
867 lazy_open_result(svn_stream_t **stream,
868                  void *baton,
869                  apr_pool_t *result_pool,
870                  apr_pool_t *scratch_pool)
871 {
872   struct file_baton *fb = baton;
873
874   SVN_ERR(svn_stream_open_unique(stream, &fb->path_end_revision, NULL,
875                                  svn_io_file_del_on_pool_cleanup,
876                                  result_pool, scratch_pool));
877
878   return SVN_NO_ERROR;
879 }
880
881 /* An svn_delta_editor_t function.  */
882 static svn_error_t *
883 apply_textdelta(void *file_baton,
884                 const char *base_md5_digest,
885                 apr_pool_t *pool,
886                 svn_txdelta_window_handler_t *handler,
887                 void **handler_baton)
888 {
889   struct file_baton *fb = file_baton;
890   svn_stream_t *src_stream;
891   svn_stream_t *result_stream;
892   apr_pool_t *scratch_pool = fb->pool;
893
894   /* Skip *everything* within a newly tree-conflicted directory. */
895   if (fb->skip)
896     {
897       *handler = svn_delta_noop_window_handler;
898       *handler_baton = NULL;
899       return SVN_NO_ERROR;
900     }
901
902   /* If we're not sending file text, then ignore any that we receive. */
903   if (! fb->edit_baton->text_deltas)
904     {
905       /* Supply valid paths to indicate there is a text change. */
906       SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_start_revision));
907       SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_end_revision));
908
909       *handler = svn_delta_noop_window_handler;
910       *handler_baton = NULL;
911
912       return SVN_NO_ERROR;
913     }
914
915   /* We need the expected pristine file, so go get it */
916   if (!fb->added)
917     SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool));
918   else
919     SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision)));
920
921   SVN_ERR_ASSERT(fb->path_start_revision != NULL);
922
923   if (base_md5_digest != NULL)
924     {
925       svn_checksum_t *base_md5_checksum;
926
927       SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5,
928                                      base_md5_digest, scratch_pool));
929
930       if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum))
931         return svn_error_trace(svn_checksum_mismatch_err(
932                                       base_md5_checksum,
933                                       fb->start_md5_checksum,
934                                       scratch_pool,
935                                       _("Base checksum mismatch for '%s'"),
936                                       fb->path));
937     }
938
939   /* Open the file to be used as the base for second revision */
940   src_stream = svn_stream_lazyopen_create(lazy_open_source, fb, TRUE,
941                                           scratch_pool);
942
943   /* Open the file that will become the second revision after applying the
944      text delta, it starts empty */
945   result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE,
946                                              scratch_pool);
947
948   svn_txdelta_apply(src_stream,
949                     result_stream,
950                     fb->result_digest,
951                     fb->path, fb->pool,
952                     &(fb->apply_handler), &(fb->apply_baton));
953
954   *handler = window_handler;
955   *handler_baton = file_baton;
956
957   return SVN_NO_ERROR;
958 }
959
960 /* An svn_delta_editor_t function.  When the file is closed we have a temporary
961  * file containing a pristine version of the repository file. This can
962  * be compared against the working copy.
963  *
964  * ### Ignore TEXT_CHECKSUM for now.  Someday we can use it to verify
965  * ### the integrity of the file being diffed.  Done efficiently, this
966  * ### would probably involve calculating the checksum as the data is
967  * ### received, storing the final checksum in the file_baton, and
968  * ### comparing against it here.
969  */
970 static svn_error_t *
971 close_file(void *file_baton,
972            const char *expected_md5_digest,
973            apr_pool_t *pool)
974 {
975   struct file_baton *fb = file_baton;
976   struct dir_baton *pb = fb->parent_baton;
977   struct edit_baton *eb = fb->edit_baton;
978   apr_pool_t *scratch_pool;
979
980   /* Skip *everything* within a newly tree-conflicted directory. */
981   if (fb->skip)
982     {
983       svn_pool_destroy(fb->pool);
984       SVN_ERR(release_dir(pb));
985       return SVN_NO_ERROR;
986     }
987
988   scratch_pool = fb->pool;
989
990   if (expected_md5_digest && eb->text_deltas)
991     {
992       svn_checksum_t *expected_md5_checksum;
993
994       SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
995                                      expected_md5_digest, scratch_pool));
996
997       if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum))
998         return svn_error_trace(svn_checksum_mismatch_err(
999                                       expected_md5_checksum,
1000                                       fb->result_md5_checksum,
1001                                       pool,
1002                                       _("Checksum mismatch for '%s'"),
1003                                       fb->path));
1004     }
1005
1006   if (fb->added || fb->path_end_revision || fb->has_propchange)
1007     {
1008       apr_hash_t *right_props;
1009
1010       if (!fb->added && !fb->pristine_props)
1011         {
1012           /* We didn't receive a text change, so we have no pristine props.
1013              Retrieve just the props now. */
1014           SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool));
1015         }
1016
1017       if (fb->pristine_props)
1018         remove_non_prop_changes(fb->pristine_props, fb->propchanges);
1019
1020       right_props = svn_prop__patch(fb->pristine_props, fb->propchanges,
1021                                     fb->pool);
1022
1023       if (fb->added)
1024         SVN_ERR(eb->processor->file_added(fb->path,
1025                                           NULL /* copyfrom_src */,
1026                                           fb->right_source,
1027                                           NULL /* copyfrom_file */,
1028                                           fb->path_end_revision,
1029                                           NULL /* copyfrom_props */,
1030                                           right_props,
1031                                           fb->pfb,
1032                                           eb->processor,
1033                                           fb->pool));
1034       else
1035         SVN_ERR(eb->processor->file_changed(fb->path,
1036                                             fb->left_source,
1037                                             fb->right_source,
1038                                             fb->path_end_revision
1039                                                     ? fb->path_start_revision
1040                                                     : NULL,
1041                                             fb->path_end_revision,
1042                                             fb->pristine_props,
1043                                             right_props,
1044                                             (fb->path_end_revision != NULL),
1045                                             fb->propchanges,
1046                                             fb->pfb,
1047                                             eb->processor,
1048                                             fb->pool));
1049     }
1050
1051   svn_pool_destroy(fb->pool); /* Destroy file and scratch pool */
1052
1053   SVN_ERR(release_dir(pb));
1054
1055   return SVN_NO_ERROR;
1056 }
1057
1058 /* Report any accumulated prop changes via the 'dir_props_changed' callback,
1059  * and then call the 'dir_closed' callback.  Notify about any deleted paths
1060  * within this directory that have not already been notified, and then about
1061  * this directory itself (unless it was added, in which case the notification
1062  * was done at that time).
1063  *
1064  * An svn_delta_editor_t function.  */
1065 static svn_error_t *
1066 close_directory(void *dir_baton,
1067                 apr_pool_t *pool)
1068 {
1069   struct dir_baton *db = dir_baton;
1070   struct edit_baton *eb = db->edit_baton;
1071   apr_pool_t *scratch_pool;
1072   apr_hash_t *pristine_props;
1073   svn_boolean_t send_changed = FALSE;
1074
1075   scratch_pool = db->pool;
1076
1077   if ((db->has_propchange || db->added) && !db->skip)
1078     {
1079       if (db->added)
1080         {
1081           pristine_props = eb->empty_hash;
1082         }
1083       else
1084         {
1085           SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props,
1086                                   db->path, db->base_revision, 0, scratch_pool));
1087         }
1088
1089       if (db->propchanges->nelts > 0)
1090         {
1091           remove_non_prop_changes(pristine_props, db->propchanges);
1092         }
1093
1094       if (db->propchanges->nelts > 0 || db->added)
1095         {
1096           apr_hash_t *right_props;
1097
1098           right_props = svn_prop__patch(pristine_props, db->propchanges,
1099                                         scratch_pool);
1100
1101           if (db->added)
1102             {
1103               SVN_ERR(eb->processor->dir_added(db->path,
1104                                            NULL /* copyfrom */,
1105                                            db->right_source,
1106                                            NULL /* copyfrom props */,
1107                                            right_props,
1108                                            db->pdb,
1109                                            eb->processor,
1110                                            db->pool));
1111             }
1112           else
1113             {
1114               SVN_ERR(eb->processor->dir_changed(db->path,
1115                                                  db->left_source,
1116                                                  db->right_source,
1117                                                  pristine_props,
1118                                                  right_props,
1119                                                  db->propchanges,
1120                                                  db->pdb,
1121                                                  eb->processor,
1122                                                  db->pool));
1123             }
1124
1125           send_changed = TRUE; /* Skip dir_closed */
1126         }
1127     }
1128
1129   if (! db->skip && !send_changed)
1130     {
1131       SVN_ERR(eb->processor->dir_closed(db->path,
1132                                         db->left_source,
1133                                         db->right_source,
1134                                         db->pdb,
1135                                         eb->processor,
1136                                         db->pool));
1137     }
1138   SVN_ERR(release_dir(db));
1139
1140   return SVN_NO_ERROR;
1141 }
1142
1143
1144 /* Record a prop change, which we will report later in close_file().
1145  *
1146  * An svn_delta_editor_t function.  */
1147 static svn_error_t *
1148 change_file_prop(void *file_baton,
1149                  const char *name,
1150                  const svn_string_t *value,
1151                  apr_pool_t *pool)
1152 {
1153   struct file_baton *fb = file_baton;
1154   svn_prop_t *propchange;
1155   svn_prop_kind_t propkind;
1156
1157   /* Skip *everything* within a newly tree-conflicted directory. */
1158   if (fb->skip)
1159     return SVN_NO_ERROR;
1160
1161   propkind = svn_property_kind2(name);
1162   if (propkind == svn_prop_wc_kind)
1163     return SVN_NO_ERROR;
1164   else if (propkind == svn_prop_regular_kind)
1165     fb->has_propchange = TRUE;
1166
1167   propchange = apr_array_push(fb->propchanges);
1168   propchange->name = apr_pstrdup(fb->pool, name);
1169   propchange->value = svn_string_dup(value, fb->pool);
1170
1171   return SVN_NO_ERROR;
1172 }
1173
1174 /* Make a note of this prop change, to be reported when the dir is closed.
1175  *
1176  * An svn_delta_editor_t function.  */
1177 static svn_error_t *
1178 change_dir_prop(void *dir_baton,
1179                 const char *name,
1180                 const svn_string_t *value,
1181                 apr_pool_t *pool)
1182 {
1183   struct dir_baton *db = dir_baton;
1184   svn_prop_t *propchange;
1185   svn_prop_kind_t propkind;
1186
1187   /* Skip *everything* within a newly tree-conflicted directory. */
1188   if (db->skip)
1189     return SVN_NO_ERROR;
1190
1191   propkind = svn_property_kind2(name);
1192   if (propkind == svn_prop_wc_kind)
1193     return SVN_NO_ERROR;
1194   else if (propkind == svn_prop_regular_kind)
1195     db->has_propchange = TRUE;
1196
1197   propchange = apr_array_push(db->propchanges);
1198   propchange->name = apr_pstrdup(db->pool, name);
1199   propchange->value = svn_string_dup(value, db->pool);
1200
1201   return SVN_NO_ERROR;
1202 }
1203
1204
1205 /* An svn_delta_editor_t function.  */
1206 static svn_error_t *
1207 close_edit(void *edit_baton,
1208            apr_pool_t *pool)
1209 {
1210   struct edit_baton *eb = edit_baton;
1211
1212   svn_pool_destroy(eb->pool);
1213
1214   return SVN_NO_ERROR;
1215 }
1216
1217 /* Notify that the node at PATH is 'missing'.
1218  * An svn_delta_editor_t function.  */
1219 static svn_error_t *
1220 absent_directory(const char *path,
1221                  void *parent_baton,
1222                  apr_pool_t *pool)
1223 {
1224   struct dir_baton *pb = parent_baton;
1225   struct edit_baton *eb = pb->edit_baton;
1226
1227   SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1228
1229   return SVN_NO_ERROR;
1230 }
1231
1232
1233 /* Notify that the node at PATH is 'missing'.
1234  * An svn_delta_editor_t function.  */
1235 static svn_error_t *
1236 absent_file(const char *path,
1237             void *parent_baton,
1238             apr_pool_t *pool)
1239 {
1240   struct dir_baton *pb = parent_baton;
1241   struct edit_baton *eb = pb->edit_baton;
1242
1243   SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1244
1245   return SVN_NO_ERROR;
1246 }
1247
1248 static svn_error_t *
1249 fetch_kind_func(svn_node_kind_t *kind,
1250                 void *baton,
1251                 const char *path,
1252                 svn_revnum_t base_revision,
1253                 apr_pool_t *scratch_pool)
1254 {
1255   struct edit_baton *eb = baton;
1256
1257   if (!SVN_IS_VALID_REVNUM(base_revision))
1258     base_revision = eb->revision;
1259
1260   SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1261                             scratch_pool));
1262
1263   return SVN_NO_ERROR;
1264 }
1265
1266 static svn_error_t *
1267 fetch_props_func(apr_hash_t **props,
1268                  void *baton,
1269                  const char *path,
1270                  svn_revnum_t base_revision,
1271                  apr_pool_t *result_pool,
1272                  apr_pool_t *scratch_pool)
1273 {
1274   struct edit_baton *eb = baton;
1275   svn_node_kind_t node_kind;
1276
1277   if (!SVN_IS_VALID_REVNUM(base_revision))
1278     base_revision = eb->revision;
1279
1280   SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1281                             scratch_pool));
1282
1283   if (node_kind == svn_node_file)
1284     {
1285       SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1286                               NULL, NULL, props, result_pool));
1287     }
1288   else if (node_kind == svn_node_dir)
1289     {
1290       apr_array_header_t *tmp_props;
1291
1292       SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1293                               base_revision, 0 /* Dirent fields */,
1294                               result_pool));
1295       tmp_props = svn_prop_hash_to_array(*props, result_pool);
1296       SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1297                                    result_pool));
1298       *props = svn_prop_array_to_hash(tmp_props, result_pool);
1299     }
1300   else
1301     {
1302       *props = apr_hash_make(result_pool);
1303     }
1304
1305   return SVN_NO_ERROR;
1306 }
1307
1308 static svn_error_t *
1309 fetch_base_func(const char **filename,
1310                 void *baton,
1311                 const char *path,
1312                 svn_revnum_t base_revision,
1313                 apr_pool_t *result_pool,
1314                 apr_pool_t *scratch_pool)
1315 {
1316   struct edit_baton *eb = baton;
1317   svn_stream_t *fstream;
1318   svn_error_t *err;
1319
1320   if (!SVN_IS_VALID_REVNUM(base_revision))
1321     base_revision = eb->revision;
1322
1323   SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1324                                  svn_io_file_del_on_pool_cleanup,
1325                                  result_pool, scratch_pool));
1326
1327   err = svn_ra_get_file(eb->ra_session, path, base_revision,
1328                         fstream, NULL, NULL, scratch_pool);
1329   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1330     {
1331       svn_error_clear(err);
1332       SVN_ERR(svn_stream_close(fstream));
1333
1334       *filename = NULL;
1335       return SVN_NO_ERROR;
1336     }
1337   else if (err)
1338     return svn_error_trace(err);
1339
1340   SVN_ERR(svn_stream_close(fstream));
1341
1342   return SVN_NO_ERROR;
1343 }
1344
1345 /* Create a repository diff editor and baton.  */
1346 svn_error_t *
1347 svn_client__get_diff_editor2(const svn_delta_editor_t **editor,
1348                              void **edit_baton,
1349                              svn_ra_session_t *ra_session,
1350                              svn_depth_t depth,
1351                              svn_revnum_t revision,
1352                              svn_boolean_t text_deltas,
1353                              const svn_diff_tree_processor_t *processor,
1354                              svn_cancel_func_t cancel_func,
1355                              void *cancel_baton,
1356                              apr_pool_t *result_pool)
1357 {
1358   apr_pool_t *editor_pool = svn_pool_create(result_pool);
1359   svn_delta_editor_t *tree_editor = svn_delta_default_editor(editor_pool);
1360   struct edit_baton *eb = apr_pcalloc(editor_pool, sizeof(*eb));
1361   svn_delta_shim_callbacks_t *shim_callbacks =
1362                                 svn_delta_shim_callbacks_default(editor_pool);
1363
1364   eb->pool = editor_pool;
1365   eb->depth = depth;
1366
1367   eb->processor = processor;
1368
1369   eb->ra_session = ra_session;
1370
1371   eb->revision = revision;
1372   eb->target_revision = SVN_INVALID_REVNUM;
1373   eb->empty_file = NULL;
1374   eb->empty_hash = apr_hash_make(eb->pool);
1375   eb->text_deltas = text_deltas;
1376   eb->cancel_func = cancel_func;
1377   eb->cancel_baton = cancel_baton;
1378
1379   tree_editor->set_target_revision = set_target_revision;
1380   tree_editor->open_root = open_root;
1381   tree_editor->delete_entry = delete_entry;
1382   tree_editor->add_directory = add_directory;
1383   tree_editor->open_directory = open_directory;
1384   tree_editor->add_file = add_file;
1385   tree_editor->open_file = open_file;
1386   tree_editor->apply_textdelta = apply_textdelta;
1387   tree_editor->close_file = close_file;
1388   tree_editor->close_directory = close_directory;
1389   tree_editor->change_file_prop = change_file_prop;
1390   tree_editor->change_dir_prop = change_dir_prop;
1391   tree_editor->close_edit = close_edit;
1392   tree_editor->absent_directory = absent_directory;
1393   tree_editor->absent_file = absent_file;
1394
1395   SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1396                                             tree_editor, eb,
1397                                             editor, edit_baton,
1398                                             eb->pool));
1399
1400   shim_callbacks->fetch_kind_func = fetch_kind_func;
1401   shim_callbacks->fetch_props_func = fetch_props_func;
1402   shim_callbacks->fetch_base_func = fetch_base_func;
1403   shim_callbacks->fetch_baton = eb;
1404
1405   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1406                                    NULL, NULL, shim_callbacks,
1407                                    result_pool, result_pool));
1408
1409   return SVN_NO_ERROR;
1410 }