repos_diff.c revision 362181
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#include "private/svn_sorts_private.h"
55
56/* Overall crawler editor baton.  */
57struct edit_baton {
58  /* The passed depth */
59  svn_depth_t depth;
60
61  /* The result processor */
62  const svn_diff_tree_processor_t *processor;
63
64  /* RA_SESSION is the open session for making requests to the RA layer */
65  svn_ra_session_t *ra_session;
66
67  /* The rev1 from the '-r Rev1:Rev2' command line option */
68  svn_revnum_t revision;
69
70  /* The rev2 from the '-r Rev1:Rev2' option, specifically set by
71     set_target_revision(). */
72  svn_revnum_t target_revision;
73
74  /* The path to a temporary empty file used for add/delete
75     differences.  The path is cached here so that it can be reused,
76     since all empty files are the same. */
77  const char *empty_file;
78
79  /* Empty hash used for adds. */
80  apr_hash_t *empty_hash;
81
82  /* Whether to report text deltas */
83  svn_boolean_t text_deltas;
84
85  /* A callback used to see if the client wishes to cancel the running
86     operation. */
87  svn_cancel_func_t cancel_func;
88
89  /* A baton to pass to the cancellation callback. */
90  void *cancel_baton;
91
92  apr_pool_t *pool;
93};
94
95typedef struct deleted_path_notify_t
96{
97  svn_node_kind_t kind;
98  svn_wc_notify_action_t action;
99  svn_wc_notify_state_t state;
100  svn_boolean_t tree_conflicted;
101} deleted_path_notify_t;
102
103/* Directory level baton.
104 */
105struct dir_baton {
106  /* Gets set if the directory is added rather than replaced/unchanged. */
107  svn_boolean_t added;
108
109  /* Gets set if this operation caused a tree-conflict on this directory
110   * (does not show tree-conflicts persisting from before this operation). */
111  svn_boolean_t tree_conflicted;
112
113  /* If TRUE, this node is skipped entirely.
114   * This is used to skip all children of a tree-conflicted
115   * directory without setting TREE_CONFLICTED to TRUE everywhere. */
116  svn_boolean_t skip;
117
118  /* If TRUE, all children of this directory are skipped. */
119  svn_boolean_t skip_children;
120
121  /* The path of the directory within the repository */
122  const char *path;
123
124  /* The baton for the parent directory, or null if this is the root of the
125     hierarchy to be compared. */
126  struct dir_baton *parent_baton;
127
128  /* The overall crawler editor baton. */
129  struct edit_baton *edit_baton;
130
131  /* A cache of any property changes (svn_prop_t) received for this dir. */
132  apr_array_header_t *propchanges;
133
134  /* Boolean indicating whether a node property was changed */
135  svn_boolean_t has_propchange;
136
137  /* Baton for svn_diff_tree_processor_t */
138  void *pdb;
139  svn_diff_source_t *left_source;
140  svn_diff_source_t *right_source;
141
142  /* The pool passed in by add_dir, open_dir, or open_root.
143     Also, the pool this dir baton is allocated in. */
144  apr_pool_t *pool;
145
146  /* Base revision of directory. */
147  svn_revnum_t base_revision;
148
149  /* Number of users of baton. Its pool will be destroyed 0 */
150  int users;
151};
152
153/* File level baton.
154 */
155struct file_baton {
156  /* Reference to parent baton */
157  struct dir_baton *parent_baton;
158
159  /* Gets set if the file is added rather than replaced. */
160  svn_boolean_t added;
161
162  /* Gets set if this operation caused a tree-conflict on this file
163   * (does not show tree-conflicts persisting from before this operation). */
164  svn_boolean_t tree_conflicted;
165
166  /* If TRUE, this node is skipped entirely.
167   * This is currently used to skip all children of a tree-conflicted
168   * directory. */
169  svn_boolean_t skip;
170
171  /* The path of the file within the repository */
172  const char *path;
173
174  /* The path and APR file handle to the temporary file that contains the
175     first repository version.  Also, the pristine-property list of
176     this file. */
177  const char *path_start_revision;
178  apr_hash_t *pristine_props;
179  svn_revnum_t base_revision;
180
181  /* The path and APR file handle to the temporary file that contains the
182     second repository version.  These fields are set when processing
183     textdelta and file deletion, and will be NULL if there's no
184     textual difference between the two revisions. */
185  const char *path_end_revision;
186
187  /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */
188  svn_txdelta_window_handler_t apply_handler;
189  void *apply_baton;
190
191  /* The overall crawler editor baton. */
192  struct edit_baton *edit_baton;
193
194  /* Holds the checksum of the start revision file */
195  svn_checksum_t *start_md5_checksum;
196
197  /* Holds the resulting md5 digest of a textdelta transform */
198  unsigned char result_digest[APR_MD5_DIGESTSIZE];
199  svn_checksum_t *result_md5_checksum;
200
201  /* A cache of any property changes (svn_prop_t) received for this file. */
202  apr_array_header_t *propchanges;
203
204  /* Boolean indicating whether a node property was changed */
205  svn_boolean_t has_propchange;
206
207  /* Baton for svn_diff_tree_processor_t */
208  void *pfb;
209  svn_diff_source_t *left_source;
210  svn_diff_source_t *right_source;
211
212  /* The pool passed in by add_file or open_file.
213     Also, the pool this file_baton is allocated in. */
214  apr_pool_t *pool;
215};
216
217/* Create a new directory baton for PATH in POOL.  ADDED is set if
218 * this directory is being added rather than replaced. PARENT_BATON is
219 * the baton of the parent directory (or NULL if this is the root of
220 * the comparison hierarchy). The directory and its parent may or may
221 * not exist in the working copy.  EDIT_BATON is the overall crawler
222 * editor baton.
223 */
224static struct dir_baton *
225make_dir_baton(const char *path,
226               struct dir_baton *parent_baton,
227               struct edit_baton *edit_baton,
228               svn_boolean_t added,
229               svn_revnum_t base_revision,
230               apr_pool_t *result_pool)
231{
232  apr_pool_t *dir_pool = svn_pool_create(result_pool);
233  struct dir_baton *dir_baton = apr_pcalloc(dir_pool, sizeof(*dir_baton));
234
235  dir_baton->parent_baton = parent_baton;
236  dir_baton->edit_baton = edit_baton;
237  dir_baton->added = added;
238  dir_baton->tree_conflicted = FALSE;
239  dir_baton->skip = FALSE;
240  dir_baton->skip_children = FALSE;
241  dir_baton->pool = dir_pool;
242  dir_baton->path = apr_pstrdup(dir_pool, path);
243  dir_baton->propchanges  = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
244  dir_baton->base_revision = base_revision;
245  dir_baton->users++;
246
247  if (parent_baton)
248    parent_baton->users++;
249
250  return dir_baton;
251}
252
253/* New function. Called by everyone who has a reference when done */
254static svn_error_t *
255release_dir(struct dir_baton *db)
256{
257  assert(db->users > 0);
258
259  db->users--;
260  if (db->users)
261     return SVN_NO_ERROR;
262
263  {
264    struct dir_baton *pb = db->parent_baton;
265
266    svn_pool_destroy(db->pool);
267
268    if (pb != NULL)
269      SVN_ERR(release_dir(pb));
270  }
271
272  return SVN_NO_ERROR;
273}
274
275/* Create a new file baton for PATH in POOL, which is a child of
276 * directory PARENT_PATH. ADDED is set if this file is being added
277 * rather than replaced.  EDIT_BATON is a pointer to the global edit
278 * baton.
279 */
280static struct file_baton *
281make_file_baton(const char *path,
282                struct dir_baton *parent_baton,
283                svn_boolean_t added,
284                apr_pool_t *result_pool)
285{
286  apr_pool_t *file_pool = svn_pool_create(result_pool);
287  struct file_baton *file_baton = apr_pcalloc(file_pool, sizeof(*file_baton));
288
289  file_baton->parent_baton = parent_baton;
290  file_baton->edit_baton = parent_baton->edit_baton;
291  file_baton->added = added;
292  file_baton->tree_conflicted = FALSE;
293  file_baton->skip = FALSE;
294  file_baton->pool = file_pool;
295  file_baton->path = apr_pstrdup(file_pool, path);
296  file_baton->propchanges  = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
297  file_baton->base_revision = parent_baton->edit_baton->revision;
298
299  parent_baton->users++;
300
301  return file_baton;
302}
303
304/* Get revision FB->base_revision of the file described by FB from the
305 * repository, through FB->edit_baton->ra_session.
306 *
307 * Unless PROPS_ONLY is true:
308 *   Set FB->path_start_revision to the path of a new temporary file containing
309 *   the file's text.
310 *   Set FB->start_md5_checksum to that file's MD-5 checksum.
311 *   Install a pool cleanup handler on FB->pool to delete the file.
312 *
313 * Always:
314 *   Set FB->pristine_props to a new hash containing the file's properties.
315 *
316 * Allocate all results in FB->pool.
317 */
318static svn_error_t *
319get_file_from_ra(struct file_baton *fb,
320                 svn_boolean_t props_only,
321                 apr_pool_t *scratch_pool)
322{
323  if (! props_only)
324    {
325      svn_stream_t *fstream;
326
327      SVN_ERR(svn_stream_open_unique(&fstream, &(fb->path_start_revision),
328                                     NULL, svn_io_file_del_on_pool_cleanup,
329                                     fb->pool, scratch_pool));
330
331      fstream = svn_stream_checksummed2(fstream, NULL, &fb->start_md5_checksum,
332                                        svn_checksum_md5, TRUE, fb->pool);
333
334      /* Retrieve the file and its properties */
335      SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
336                              fb->path,
337                              fb->base_revision,
338                              fstream, NULL,
339                              &(fb->pristine_props),
340                              fb->pool));
341      SVN_ERR(svn_stream_close(fstream));
342    }
343  else
344    {
345      SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
346                              fb->path,
347                              fb->base_revision,
348                              NULL, NULL,
349                              &(fb->pristine_props),
350                              fb->pool));
351    }
352
353  return SVN_NO_ERROR;
354}
355
356/* Remove every no-op property change from CHANGES: that is, remove every
357   entry in which the target value is the same as the value of the
358   corresponding property in PRISTINE_PROPS.
359
360     Issue #3657 'dav update report handler in skelta mode can cause
361     spurious conflicts'.  When communicating with the repository via ra_serf,
362     the change_dir_prop and change_file_prop svn_delta_editor_t
363     callbacks are called (obviously) when a directory or file property has
364     changed between the start and end of the edit.  Less obvious however,
365     is that these callbacks may be made describing *all* of the properties
366     on FILE_BATON->PATH when using the DAV providers, not just the change(s).
367     (Specifically ra_serf does it for diff/merge/update/switch).
368
369     This means that the change_[file|dir]_prop svn_delta_editor_t callbacks
370     may be made where there are no property changes (i.e. a noop change of
371     NAME from VALUE to VALUE).  Normally this is harmless, but during a
372     merge it can result in spurious conflicts if the WC's pristine property
373     NAME has a value other than VALUE.  In an ideal world the mod_dav_svn
374     update report handler, when in 'skelta' mode and describing changes to
375     a path on which a property has changed, wouldn't ask the client to later
376     fetch all properties and figure out what has changed itself.  The server
377     already knows which properties have changed!
378
379     Regardless, such a change is not yet implemented, and even when it is,
380     the client should DTRT with regard to older servers which behave this
381     way.  Hence this little hack:  We populate FILE_BATON->PROPCHANGES only
382     with *actual* property changes.
383
384     See https://issues.apache.org/jira/browse/SVN-3657#desc9 and
385     http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details.
386 */
387static svn_error_t *
388remove_non_prop_changes(apr_hash_t *pristine_props,
389                        apr_array_header_t *changes)
390{
391  int i;
392
393  /* For added nodes, there is nothing to filter. */
394  if (apr_hash_count(pristine_props) == 0)
395    return SVN_NO_ERROR;
396
397  for (i = 0; i < changes->nelts; i++)
398    {
399      svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t);
400
401      if (change->value)
402        {
403          const svn_string_t *old_val = svn_hash_gets(pristine_props,
404                                                      change->name);
405
406          if (old_val && svn_string_compare(old_val, change->value))
407            {
408              /* Remove the matching change and re-check the current index */
409              SVN_ERR(svn_sort__array_delete2(changes, i, 1));
410              i--;
411            }
412        }
413    }
414  return SVN_NO_ERROR;
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 */
420static svn_error_t *
421get_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.  */
438static svn_error_t *
439set_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 */
450static svn_error_t *
451open_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 */
481static svn_error_t *
482diff_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. */
531static svn_error_t *
532diff_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 = apr_hash_this_key(hi);
585          svn_dirent_t *dirent = apr_hash_this_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.  */
619static svn_error_t *
620delete_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.  */
662static svn_error_t *
663add_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.  */
706static svn_error_t *
707open_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.  */
747static svn_error_t *
748add_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.  */
789static svn_error_t *
790open_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.  */
828static svn_error_t *
829window_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. */
847static svn_error_t *
848lazy_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. */
862static svn_error_t *
863lazy_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.  */
878static svn_error_t *
879apply_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 */
966static svn_error_t *
967close_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        SVN_ERR(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.  */
1061static svn_error_t *
1062close_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          SVN_ERR(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.  */
1143static svn_error_t *
1144change_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 = svn_string_dup(value, fb->pool);
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.  */
1173static svn_error_t *
1174change_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 = svn_string_dup(value, db->pool);
1196
1197  return SVN_NO_ERROR;
1198}
1199
1200
1201/* An svn_delta_editor_t function.  */
1202static svn_error_t *
1203close_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.  */
1215static svn_error_t *
1216absent_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.  */
1231static svn_error_t *
1232absent_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
1244static svn_error_t *
1245fetch_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
1262static svn_error_t *
1263fetch_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
1304static svn_error_t *
1305fetch_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.  */
1342svn_error_t *
1343svn_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->target_revision = SVN_INVALID_REVNUM;
1369  eb->empty_file = NULL;
1370  eb->empty_hash = apr_hash_make(eb->pool);
1371  eb->text_deltas = text_deltas;
1372  eb->cancel_func = cancel_func;
1373  eb->cancel_baton = cancel_baton;
1374
1375  tree_editor->set_target_revision = set_target_revision;
1376  tree_editor->open_root = open_root;
1377  tree_editor->delete_entry = delete_entry;
1378  tree_editor->add_directory = add_directory;
1379  tree_editor->open_directory = open_directory;
1380  tree_editor->add_file = add_file;
1381  tree_editor->open_file = open_file;
1382  tree_editor->apply_textdelta = apply_textdelta;
1383  tree_editor->close_file = close_file;
1384  tree_editor->close_directory = close_directory;
1385  tree_editor->change_file_prop = change_file_prop;
1386  tree_editor->change_dir_prop = change_dir_prop;
1387  tree_editor->close_edit = close_edit;
1388  tree_editor->absent_directory = absent_directory;
1389  tree_editor->absent_file = absent_file;
1390
1391  SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1392                                            tree_editor, eb,
1393                                            editor, edit_baton,
1394                                            eb->pool));
1395
1396  shim_callbacks->fetch_kind_func = fetch_kind_func;
1397  shim_callbacks->fetch_props_func = fetch_props_func;
1398  shim_callbacks->fetch_base_func = fetch_base_func;
1399  shim_callbacks->fetch_baton = eb;
1400
1401  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1402                                   NULL, NULL, shim_callbacks,
1403                                   result_pool, result_pool));
1404
1405  return SVN_NO_ERROR;
1406}
1407