1251881Speter/*
2251881Speter * repos_diff.c -- The diff editor for comparing two repository versions
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* This code uses an editor driven by a tree delta between two
25251881Speter * repository revisions (REV1 and REV2). For each file encountered in
26251881Speter * the delta the editor constructs two temporary files, one for each
27251881Speter * revision. This necessitates a separate request for the REV1 version
28251881Speter * of the file when the delta shows the file being modified or
29251881Speter * deleted. Files that are added by the delta do not require a
30251881Speter * separate request, the REV1 version is empty and the delta is
31251881Speter * sufficient to construct the REV2 version. When both versions of
32251881Speter * each file have been created the diff callback is invoked to display
33251881Speter * the difference between the two files.  */
34251881Speter
35251881Speter#include <apr_uri.h>
36251881Speter#include <apr_md5.h>
37251881Speter#include <assert.h>
38251881Speter
39251881Speter#include "svn_checksum.h"
40251881Speter#include "svn_hash.h"
41251881Speter#include "svn_wc.h"
42251881Speter#include "svn_pools.h"
43251881Speter#include "svn_dirent_uri.h"
44251881Speter#include "svn_path.h"
45251881Speter#include "svn_io.h"
46251881Speter#include "svn_props.h"
47251881Speter#include "svn_private_config.h"
48251881Speter
49251881Speter#include "client.h"
50251881Speter
51251881Speter#include "private/svn_subr_private.h"
52251881Speter#include "private/svn_wc_private.h"
53251881Speter#include "private/svn_editor.h"
54251881Speter
55251881Speter/* Overall crawler editor baton.  */
56251881Speterstruct edit_baton {
57251881Speter  /* The passed depth */
58251881Speter  svn_depth_t depth;
59251881Speter
60251881Speter  /* The result processor */
61251881Speter  const svn_diff_tree_processor_t *processor;
62251881Speter
63251881Speter  /* RA_SESSION is the open session for making requests to the RA layer */
64251881Speter  svn_ra_session_t *ra_session;
65251881Speter
66251881Speter  /* The rev1 from the '-r Rev1:Rev2' command line option */
67251881Speter  svn_revnum_t revision;
68251881Speter
69251881Speter  /* The rev2 from the '-r Rev1:Rev2' option, specifically set by
70251881Speter     set_target_revision(). */
71251881Speter  svn_revnum_t target_revision;
72251881Speter
73251881Speter  /* The path to a temporary empty file used for add/delete
74251881Speter     differences.  The path is cached here so that it can be reused,
75251881Speter     since all empty files are the same. */
76251881Speter  const char *empty_file;
77251881Speter
78251881Speter  /* Empty hash used for adds. */
79251881Speter  apr_hash_t *empty_hash;
80251881Speter
81251881Speter  /* Whether to report text deltas */
82251881Speter  svn_boolean_t text_deltas;
83251881Speter
84251881Speter  /* A callback used to see if the client wishes to cancel the running
85251881Speter     operation. */
86251881Speter  svn_cancel_func_t cancel_func;
87251881Speter
88251881Speter  /* A baton to pass to the cancellation callback. */
89251881Speter  void *cancel_baton;
90251881Speter
91251881Speter  apr_pool_t *pool;
92251881Speter};
93251881Speter
94251881Spetertypedef struct deleted_path_notify_t
95251881Speter{
96251881Speter  svn_node_kind_t kind;
97251881Speter  svn_wc_notify_action_t action;
98251881Speter  svn_wc_notify_state_t state;
99251881Speter  svn_boolean_t tree_conflicted;
100251881Speter} deleted_path_notify_t;
101251881Speter
102251881Speter/* Directory level baton.
103251881Speter */
104251881Speterstruct dir_baton {
105251881Speter  /* Gets set if the directory is added rather than replaced/unchanged. */
106251881Speter  svn_boolean_t added;
107251881Speter
108251881Speter  /* Gets set if this operation caused a tree-conflict on this directory
109251881Speter   * (does not show tree-conflicts persisting from before this operation). */
110251881Speter  svn_boolean_t tree_conflicted;
111251881Speter
112251881Speter  /* If TRUE, this node is skipped entirely.
113251881Speter   * This is used to skip all children of a tree-conflicted
114251881Speter   * directory without setting TREE_CONFLICTED to TRUE everywhere. */
115251881Speter  svn_boolean_t skip;
116251881Speter
117251881Speter  /* If TRUE, all children of this directory are skipped. */
118251881Speter  svn_boolean_t skip_children;
119251881Speter
120251881Speter  /* The path of the directory within the repository */
121251881Speter  const char *path;
122251881Speter
123251881Speter  /* The baton for the parent directory, or null if this is the root of the
124251881Speter     hierarchy to be compared. */
125251881Speter  struct dir_baton *parent_baton;
126251881Speter
127251881Speter  /* The overall crawler editor baton. */
128251881Speter  struct edit_baton *edit_baton;
129251881Speter
130251881Speter  /* A cache of any property changes (svn_prop_t) received for this dir. */
131251881Speter  apr_array_header_t *propchanges;
132251881Speter
133251881Speter  /* Boolean indicating whether a node property was changed */
134251881Speter  svn_boolean_t has_propchange;
135251881Speter
136251881Speter  /* Baton for svn_diff_tree_processor_t */
137251881Speter  void *pdb;
138251881Speter  svn_diff_source_t *left_source;
139251881Speter  svn_diff_source_t *right_source;
140251881Speter
141251881Speter  /* The pool passed in by add_dir, open_dir, or open_root.
142251881Speter     Also, the pool this dir baton is allocated in. */
143251881Speter  apr_pool_t *pool;
144251881Speter
145251881Speter  /* Base revision of directory. */
146251881Speter  svn_revnum_t base_revision;
147251881Speter
148251881Speter  /* Number of users of baton. Its pool will be destroyed 0 */
149251881Speter  int users;
150251881Speter};
151251881Speter
152251881Speter/* File level baton.
153251881Speter */
154251881Speterstruct file_baton {
155251881Speter  /* Reference to parent baton */
156251881Speter  struct dir_baton *parent_baton;
157251881Speter
158251881Speter  /* Gets set if the file is added rather than replaced. */
159251881Speter  svn_boolean_t added;
160251881Speter
161251881Speter  /* Gets set if this operation caused a tree-conflict on this file
162251881Speter   * (does not show tree-conflicts persisting from before this operation). */
163251881Speter  svn_boolean_t tree_conflicted;
164251881Speter
165251881Speter  /* If TRUE, this node is skipped entirely.
166251881Speter   * This is currently used to skip all children of a tree-conflicted
167251881Speter   * directory. */
168251881Speter  svn_boolean_t skip;
169251881Speter
170251881Speter  /* The path of the file within the repository */
171251881Speter  const char *path;
172251881Speter
173251881Speter  /* The path and APR file handle to the temporary file that contains the
174251881Speter     first repository version.  Also, the pristine-property list of
175251881Speter     this file. */
176251881Speter  const char *path_start_revision;
177251881Speter  apr_hash_t *pristine_props;
178251881Speter  svn_revnum_t base_revision;
179251881Speter
180251881Speter  /* The path and APR file handle to the temporary file that contains the
181251881Speter     second repository version.  These fields are set when processing
182251881Speter     textdelta and file deletion, and will be NULL if there's no
183251881Speter     textual difference between the two revisions. */
184251881Speter  const char *path_end_revision;
185251881Speter
186251881Speter  /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */
187251881Speter  svn_txdelta_window_handler_t apply_handler;
188251881Speter  void *apply_baton;
189251881Speter
190251881Speter  /* The overall crawler editor baton. */
191251881Speter  struct edit_baton *edit_baton;
192251881Speter
193251881Speter  /* Holds the checksum of the start revision file */
194251881Speter  svn_checksum_t *start_md5_checksum;
195251881Speter
196251881Speter  /* Holds the resulting md5 digest of a textdelta transform */
197251881Speter  unsigned char result_digest[APR_MD5_DIGESTSIZE];
198251881Speter  svn_checksum_t *result_md5_checksum;
199251881Speter
200251881Speter  /* A cache of any property changes (svn_prop_t) received for this file. */
201251881Speter  apr_array_header_t *propchanges;
202251881Speter
203251881Speter  /* Boolean indicating whether a node property was changed */
204251881Speter  svn_boolean_t has_propchange;
205251881Speter
206251881Speter  /* Baton for svn_diff_tree_processor_t */
207251881Speter  void *pfb;
208251881Speter  svn_diff_source_t *left_source;
209251881Speter  svn_diff_source_t *right_source;
210251881Speter
211251881Speter  /* The pool passed in by add_file or open_file.
212251881Speter     Also, the pool this file_baton is allocated in. */
213251881Speter  apr_pool_t *pool;
214251881Speter};
215251881Speter
216251881Speter/* Create a new directory baton for PATH in POOL.  ADDED is set if
217251881Speter * this directory is being added rather than replaced. PARENT_BATON is
218251881Speter * the baton of the parent directory (or NULL if this is the root of
219251881Speter * the comparison hierarchy). The directory and its parent may or may
220251881Speter * not exist in the working copy.  EDIT_BATON is the overall crawler
221251881Speter * editor baton.
222251881Speter */
223251881Speterstatic struct dir_baton *
224251881Spetermake_dir_baton(const char *path,
225251881Speter               struct dir_baton *parent_baton,
226251881Speter               struct edit_baton *edit_baton,
227251881Speter               svn_boolean_t added,
228251881Speter               svn_revnum_t base_revision,
229251881Speter               apr_pool_t *result_pool)
230251881Speter{
231251881Speter  apr_pool_t *dir_pool = svn_pool_create(result_pool);
232251881Speter  struct dir_baton *dir_baton = apr_pcalloc(dir_pool, sizeof(*dir_baton));
233251881Speter
234251881Speter  dir_baton->parent_baton = parent_baton;
235251881Speter  dir_baton->edit_baton = edit_baton;
236251881Speter  dir_baton->added = added;
237251881Speter  dir_baton->tree_conflicted = FALSE;
238251881Speter  dir_baton->skip = FALSE;
239251881Speter  dir_baton->skip_children = FALSE;
240251881Speter  dir_baton->pool = dir_pool;
241251881Speter  dir_baton->path = apr_pstrdup(dir_pool, path);
242251881Speter  dir_baton->propchanges  = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
243251881Speter  dir_baton->base_revision = base_revision;
244251881Speter  dir_baton->users++;
245251881Speter
246251881Speter  if (parent_baton)
247251881Speter    parent_baton->users++;
248251881Speter
249251881Speter  return dir_baton;
250251881Speter}
251251881Speter
252251881Speter/* New function. Called by everyone who has a reference when done */
253251881Speterstatic svn_error_t *
254251881Speterrelease_dir(struct dir_baton *db)
255251881Speter{
256251881Speter  assert(db->users > 0);
257251881Speter
258251881Speter  db->users--;
259251881Speter  if (db->users)
260251881Speter     return SVN_NO_ERROR;
261251881Speter
262251881Speter  {
263251881Speter    struct dir_baton *pb = db->parent_baton;
264251881Speter
265251881Speter    svn_pool_destroy(db->pool);
266251881Speter
267251881Speter    if (pb != NULL)
268251881Speter      SVN_ERR(release_dir(pb));
269251881Speter  }
270251881Speter
271251881Speter  return SVN_NO_ERROR;
272251881Speter}
273251881Speter
274251881Speter/* Create a new file baton for PATH in POOL, which is a child of
275251881Speter * directory PARENT_PATH. ADDED is set if this file is being added
276251881Speter * rather than replaced.  EDIT_BATON is a pointer to the global edit
277251881Speter * baton.
278251881Speter */
279251881Speterstatic struct file_baton *
280251881Spetermake_file_baton(const char *path,
281251881Speter                struct dir_baton *parent_baton,
282251881Speter                svn_boolean_t added,
283251881Speter                apr_pool_t *result_pool)
284251881Speter{
285251881Speter  apr_pool_t *file_pool = svn_pool_create(result_pool);
286251881Speter  struct file_baton *file_baton = apr_pcalloc(file_pool, sizeof(*file_baton));
287251881Speter
288251881Speter  file_baton->parent_baton = parent_baton;
289251881Speter  file_baton->edit_baton = parent_baton->edit_baton;
290251881Speter  file_baton->added = added;
291251881Speter  file_baton->tree_conflicted = FALSE;
292251881Speter  file_baton->skip = FALSE;
293251881Speter  file_baton->pool = file_pool;
294251881Speter  file_baton->path = apr_pstrdup(file_pool, path);
295251881Speter  file_baton->propchanges  = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
296251881Speter  file_baton->base_revision = parent_baton->edit_baton->revision;
297251881Speter
298251881Speter  parent_baton->users++;
299251881Speter
300251881Speter  return file_baton;
301251881Speter}
302251881Speter
303251881Speter/* Get revision FB->base_revision of the file described by FB from the
304251881Speter * repository, through FB->edit_baton->ra_session.
305251881Speter *
306251881Speter * Unless PROPS_ONLY is true:
307251881Speter *   Set FB->path_start_revision to the path of a new temporary file containing
308251881Speter *   the file's text.
309251881Speter *   Set FB->start_md5_checksum to that file's MD-5 checksum.
310251881Speter *   Install a pool cleanup handler on FB->pool to delete the file.
311251881Speter *
312251881Speter * Always:
313251881Speter *   Set FB->pristine_props to a new hash containing the file's properties.
314251881Speter *
315251881Speter * Allocate all results in FB->pool.
316251881Speter */
317251881Speterstatic svn_error_t *
318251881Speterget_file_from_ra(struct file_baton *fb,
319251881Speter                 svn_boolean_t props_only,
320251881Speter                 apr_pool_t *scratch_pool)
321251881Speter{
322251881Speter  if (! props_only)
323251881Speter    {
324251881Speter      svn_stream_t *fstream;
325251881Speter
326251881Speter      SVN_ERR(svn_stream_open_unique(&fstream, &(fb->path_start_revision),
327251881Speter                                     NULL, svn_io_file_del_on_pool_cleanup,
328251881Speter                                     fb->pool, scratch_pool));
329251881Speter
330251881Speter      fstream = svn_stream_checksummed2(fstream, NULL, &fb->start_md5_checksum,
331299742Sdim                                        svn_checksum_md5, TRUE, fb->pool);
332251881Speter
333251881Speter      /* Retrieve the file and its properties */
334251881Speter      SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
335251881Speter                              fb->path,
336251881Speter                              fb->base_revision,
337251881Speter                              fstream, NULL,
338251881Speter                              &(fb->pristine_props),
339251881Speter                              fb->pool));
340251881Speter      SVN_ERR(svn_stream_close(fstream));
341251881Speter    }
342251881Speter  else
343251881Speter    {
344251881Speter      SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
345251881Speter                              fb->path,
346251881Speter                              fb->base_revision,
347251881Speter                              NULL, NULL,
348251881Speter                              &(fb->pristine_props),
349251881Speter                              fb->pool));
350251881Speter    }
351251881Speter
352251881Speter  return SVN_NO_ERROR;
353251881Speter}
354251881Speter
355251881Speter/* Remove every no-op property change from CHANGES: that is, remove every
356251881Speter   entry in which the target value is the same as the value of the
357251881Speter   corresponding property in PRISTINE_PROPS.
358251881Speter
359251881Speter     Issue #3657 'dav update report handler in skelta mode can cause
360251881Speter     spurious conflicts'.  When communicating with the repository via ra_serf,
361251881Speter     the change_dir_prop and change_file_prop svn_delta_editor_t
362251881Speter     callbacks are called (obviously) when a directory or file property has
363251881Speter     changed between the start and end of the edit.  Less obvious however,
364251881Speter     is that these callbacks may be made describing *all* of the properties
365251881Speter     on FILE_BATON->PATH when using the DAV providers, not just the change(s).
366251881Speter     (Specifically ra_serf does it for diff/merge/update/switch).
367251881Speter
368251881Speter     This means that the change_[file|dir]_prop svn_delta_editor_t callbacks
369251881Speter     may be made where there are no property changes (i.e. a noop change of
370251881Speter     NAME from VALUE to VALUE).  Normally this is harmless, but during a
371251881Speter     merge it can result in spurious conflicts if the WC's pristine property
372251881Speter     NAME has a value other than VALUE.  In an ideal world the mod_dav_svn
373251881Speter     update report handler, when in 'skelta' mode and describing changes to
374251881Speter     a path on which a property has changed, wouldn't ask the client to later
375251881Speter     fetch all properties and figure out what has changed itself.  The server
376251881Speter     already knows which properties have changed!
377251881Speter
378251881Speter     Regardless, such a change is not yet implemented, and even when it is,
379251881Speter     the client should DTRT with regard to older servers which behave this
380251881Speter     way.  Hence this little hack:  We populate FILE_BATON->PROPCHANGES only
381251881Speter     with *actual* property changes.
382251881Speter
383251881Speter     See http://subversion.tigris.org/issues/show_bug.cgi?id=3657#desc9 and
384251881Speter     http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details.
385251881Speter */
386251881Speterstatic void
387251881Speterremove_non_prop_changes(apr_hash_t *pristine_props,
388251881Speter                        apr_array_header_t *changes)
389251881Speter{
390251881Speter  int i;
391251881Speter
392299742Sdim  /* For added nodes, there is nothing to filter. */
393299742Sdim  if (apr_hash_count(pristine_props) == 0)
394299742Sdim    return;
395299742Sdim
396251881Speter  for (i = 0; i < changes->nelts; i++)
397251881Speter    {
398251881Speter      svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t);
399251881Speter
400251881Speter      if (change->value)
401251881Speter        {
402251881Speter          const svn_string_t *old_val = svn_hash_gets(pristine_props,
403251881Speter                                                      change->name);
404251881Speter
405251881Speter          if (old_val && svn_string_compare(old_val, change->value))
406251881Speter            {
407251881Speter              int j;
408251881Speter
409251881Speter              /* Remove the matching change by shifting the rest */
410251881Speter              for (j = i; j < changes->nelts - 1; j++)
411251881Speter                {
412251881Speter                  APR_ARRAY_IDX(changes, j, svn_prop_t)
413251881Speter                       = APR_ARRAY_IDX(changes, j+1, svn_prop_t);
414251881Speter                }
415251881Speter              changes->nelts--;
416251881Speter            }
417251881Speter        }
418251881Speter    }
419251881Speter}
420251881Speter
421251881Speter/* Get the empty file associated with the edit baton. This is cached so
422251881Speter * that it can be reused, all empty files are the same.
423251881Speter */
424251881Speterstatic svn_error_t *
425251881Speterget_empty_file(struct edit_baton *eb,
426251881Speter               const char **empty_file_path)
427251881Speter{
428251881Speter  /* Create the file if it does not exist */
429251881Speter  /* Note that we tried to use /dev/null in r857294, but
430251881Speter     that won't work on Windows: it's impossible to stat NUL */
431251881Speter  if (!eb->empty_file)
432251881Speter    SVN_ERR(svn_io_open_unique_file3(NULL, &(eb->empty_file), NULL,
433251881Speter                                     svn_io_file_del_on_pool_cleanup,
434251881Speter                                     eb->pool, eb->pool));
435251881Speter
436251881Speter  *empty_file_path = eb->empty_file;
437251881Speter
438251881Speter  return SVN_NO_ERROR;
439251881Speter}
440251881Speter
441251881Speter/* An svn_delta_editor_t function.  */
442251881Speterstatic svn_error_t *
443251881Speterset_target_revision(void *edit_baton,
444251881Speter                    svn_revnum_t target_revision,
445251881Speter                    apr_pool_t *pool)
446251881Speter{
447251881Speter  struct edit_baton *eb = edit_baton;
448251881Speter
449251881Speter  eb->target_revision = target_revision;
450251881Speter  return SVN_NO_ERROR;
451251881Speter}
452251881Speter
453251881Speter/* An svn_delta_editor_t function. The root of the comparison hierarchy */
454251881Speterstatic svn_error_t *
455251881Speteropen_root(void *edit_baton,
456251881Speter          svn_revnum_t base_revision,
457251881Speter          apr_pool_t *pool,
458251881Speter          void **root_baton)
459251881Speter{
460251881Speter  struct edit_baton *eb = edit_baton;
461251881Speter  struct dir_baton *db = make_dir_baton("", NULL, eb, FALSE, base_revision,
462251881Speter                                        eb->pool);
463251881Speter
464251881Speter  db->left_source = svn_diff__source_create(eb->revision, db->pool);
465251881Speter  db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
466251881Speter
467251881Speter  SVN_ERR(eb->processor->dir_opened(&db->pdb,
468251881Speter                                    &db->skip,
469251881Speter                                    &db->skip_children,
470251881Speter                                    "",
471251881Speter                                    db->left_source,
472251881Speter                                    db->right_source,
473251881Speter                                    NULL,
474251881Speter                                    NULL,
475251881Speter                                    eb->processor,
476251881Speter                                    db->pool,
477251881Speter                                    db->pool /* scratch_pool */));
478251881Speter
479251881Speter  *root_baton = db;
480251881Speter  return SVN_NO_ERROR;
481251881Speter}
482251881Speter
483251881Speter/* Compare a file being deleted against an empty file.
484251881Speter */
485251881Speterstatic svn_error_t *
486251881Speterdiff_deleted_file(const char *path,
487251881Speter                  struct dir_baton *db,
488251881Speter                  apr_pool_t *scratch_pool)
489251881Speter{
490251881Speter  struct edit_baton *eb = db->edit_baton;
491251881Speter  struct file_baton *fb = make_file_baton(path, db, FALSE, scratch_pool);
492251881Speter  svn_boolean_t skip = FALSE;
493251881Speter  svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
494251881Speter                                                           scratch_pool);
495251881Speter
496251881Speter  if (eb->cancel_func)
497251881Speter    SVN_ERR(eb->cancel_func(eb->cancel_baton));
498251881Speter
499251881Speter  SVN_ERR(eb->processor->file_opened(&fb->pfb, &skip, path,
500251881Speter                                     left_source,
501251881Speter                                     NULL /* right_source */,
502251881Speter                                     NULL /* copyfrom_source */,
503251881Speter                                     db->pdb,
504251881Speter                                     eb->processor,
505251881Speter                                     scratch_pool, scratch_pool));
506251881Speter
507251881Speter  if (eb->cancel_func)
508251881Speter    SVN_ERR(eb->cancel_func(eb->cancel_baton));
509251881Speter
510251881Speter  if (skip)
511251881Speter    return SVN_NO_ERROR;
512251881Speter
513251881Speter  SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool));
514251881Speter
515251881Speter  SVN_ERR(eb->processor->file_deleted(fb->path,
516251881Speter                                      left_source,
517251881Speter                                      fb->path_start_revision,
518251881Speter                                      fb->pristine_props,
519251881Speter                                      fb->pfb,
520251881Speter                                      eb->processor,
521251881Speter                                      scratch_pool));
522251881Speter
523251881Speter  return SVN_NO_ERROR;
524251881Speter}
525251881Speter
526251881Speter/* Recursively walk tree rooted at DIR (at EB->revision) in the repository,
527251881Speter * reporting all children as deleted.  Part of a workaround for issue 2333.
528251881Speter *
529251881Speter * DIR is a repository path relative to the URL in EB->ra_session.  EB is
530251881Speter * the overall crawler editor baton.  EB->revision must be a valid revision
531251881Speter * number, not SVN_INVALID_REVNUM.  Use EB->cancel_func (if not null) with
532251881Speter * EB->cancel_baton for cancellation.
533251881Speter */
534251881Speter/* ### TODO: Handle depth. */
535251881Speterstatic svn_error_t *
536251881Speterdiff_deleted_dir(const char *path,
537251881Speter                 struct dir_baton *pb,
538251881Speter                 apr_pool_t *scratch_pool)
539251881Speter{
540251881Speter  struct edit_baton *eb = pb->edit_baton;
541251881Speter  struct dir_baton *db;
542251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
543251881Speter  svn_boolean_t skip = FALSE;
544251881Speter  svn_boolean_t skip_children = FALSE;
545251881Speter  apr_hash_t *dirents = NULL;
546251881Speter  apr_hash_t *left_props = NULL;
547251881Speter  svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
548251881Speter                                                           scratch_pool);
549251881Speter  db = make_dir_baton(path, pb, pb->edit_baton, FALSE, SVN_INVALID_REVNUM,
550251881Speter                      scratch_pool);
551251881Speter
552251881Speter  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(eb->revision));
553251881Speter
554251881Speter  if (eb->cancel_func)
555251881Speter    SVN_ERR(eb->cancel_func(eb->cancel_baton));
556251881Speter
557251881Speter  SVN_ERR(eb->processor->dir_opened(&db->pdb, &skip, &skip_children,
558251881Speter                                    path,
559251881Speter                                    left_source,
560251881Speter                                    NULL /* right_source */,
561251881Speter                                    NULL /* copyfrom_source */,
562251881Speter                                    pb->pdb,
563251881Speter                                    eb->processor,
564251881Speter                                    scratch_pool, iterpool));
565251881Speter
566251881Speter  if (!skip || !skip_children)
567251881Speter    SVN_ERR(svn_ra_get_dir2(eb->ra_session,
568251881Speter                            skip_children ? NULL : &dirents,
569251881Speter                            NULL,
570251881Speter                            skip ? NULL : &left_props,
571251881Speter                            path,
572251881Speter                            eb->revision,
573251881Speter                            SVN_DIRENT_KIND,
574251881Speter                            scratch_pool));
575251881Speter
576251881Speter  /* The "old" dir will be skipped by the repository report.  If required,
577251881Speter   * crawl it recursively, diffing each file against the empty file.  This
578251881Speter   * is a workaround for issue 2333 "'svn diff URL1 URL2' not reverse of
579251881Speter   * 'svn diff URL2 URL1'". */
580251881Speter  if (! skip_children)
581251881Speter    {
582251881Speter      apr_hash_index_t *hi;
583251881Speter
584251881Speter      for (hi = apr_hash_first(scratch_pool, dirents); hi;
585251881Speter           hi = apr_hash_next(hi))
586251881Speter        {
587251881Speter          const char *child_path;
588299742Sdim          const char *name = apr_hash_this_key(hi);
589299742Sdim          svn_dirent_t *dirent = apr_hash_this_val(hi);
590251881Speter
591251881Speter          svn_pool_clear(iterpool);
592251881Speter
593251881Speter          child_path = svn_relpath_join(path, name, iterpool);
594251881Speter
595251881Speter          if (dirent->kind == svn_node_file)
596251881Speter            {
597251881Speter              SVN_ERR(diff_deleted_file(child_path, db, iterpool));
598251881Speter            }
599251881Speter          else if (dirent->kind == svn_node_dir)
600251881Speter            {
601251881Speter              SVN_ERR(diff_deleted_dir(child_path, db, iterpool));
602251881Speter            }
603251881Speter        }
604251881Speter    }
605251881Speter
606251881Speter  if (! skip)
607251881Speter    {
608251881Speter      SVN_ERR(eb->processor->dir_deleted(path,
609251881Speter                                         left_source,
610251881Speter                                         left_props,
611251881Speter                                         db->pdb,
612251881Speter                                         eb->processor,
613251881Speter                                         scratch_pool));
614251881Speter    }
615251881Speter
616251881Speter  SVN_ERR(release_dir(db));
617251881Speter
618251881Speter  svn_pool_destroy(iterpool);
619251881Speter  return SVN_NO_ERROR;
620251881Speter}
621251881Speter
622251881Speter/* An svn_delta_editor_t function.  */
623251881Speterstatic svn_error_t *
624251881Speterdelete_entry(const char *path,
625251881Speter             svn_revnum_t base_revision,
626251881Speter             void *parent_baton,
627251881Speter             apr_pool_t *pool)
628251881Speter{
629251881Speter  struct dir_baton *pb = parent_baton;
630251881Speter  struct edit_baton *eb = pb->edit_baton;
631251881Speter  svn_node_kind_t kind;
632251881Speter  apr_pool_t *scratch_pool;
633251881Speter
634251881Speter  /* Process skips. */
635251881Speter  if (pb->skip_children)
636251881Speter    return SVN_NO_ERROR;
637251881Speter
638251881Speter  scratch_pool = svn_pool_create(eb->pool);
639251881Speter
640251881Speter  /* We need to know if this is a directory or a file */
641251881Speter  SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind,
642251881Speter                            scratch_pool));
643251881Speter
644251881Speter  switch (kind)
645251881Speter    {
646251881Speter    case svn_node_file:
647251881Speter      {
648251881Speter        SVN_ERR(diff_deleted_file(path, pb, scratch_pool));
649251881Speter        break;
650251881Speter      }
651251881Speter    case svn_node_dir:
652251881Speter      {
653251881Speter        SVN_ERR(diff_deleted_dir(path, pb, scratch_pool));
654251881Speter        break;
655251881Speter      }
656251881Speter    default:
657251881Speter      break;
658251881Speter    }
659251881Speter
660251881Speter  svn_pool_destroy(scratch_pool);
661251881Speter
662251881Speter  return SVN_NO_ERROR;
663251881Speter}
664251881Speter
665251881Speter/* An svn_delta_editor_t function.  */
666251881Speterstatic svn_error_t *
667251881Speteradd_directory(const char *path,
668251881Speter              void *parent_baton,
669251881Speter              const char *copyfrom_path,
670251881Speter              svn_revnum_t copyfrom_revision,
671251881Speter              apr_pool_t *pool,
672251881Speter              void **child_baton)
673251881Speter{
674251881Speter  struct dir_baton *pb = parent_baton;
675251881Speter  struct edit_baton *eb = pb->edit_baton;
676251881Speter  struct dir_baton *db;
677251881Speter
678251881Speter  /* ### TODO: support copyfrom? */
679251881Speter
680251881Speter  db = make_dir_baton(path, pb, eb, TRUE, SVN_INVALID_REVNUM, pb->pool);
681251881Speter  *child_baton = db;
682251881Speter
683251881Speter  /* Skip *everything* within a newly tree-conflicted directory,
684251881Speter   * and directories the children of which should be skipped. */
685251881Speter  if (pb->skip_children)
686251881Speter    {
687251881Speter      db->skip = TRUE;
688251881Speter      db->skip_children = TRUE;
689251881Speter      return SVN_NO_ERROR;
690251881Speter    }
691251881Speter
692251881Speter  db->right_source = svn_diff__source_create(eb->target_revision,
693251881Speter                                             db->pool);
694251881Speter
695251881Speter  SVN_ERR(eb->processor->dir_opened(&db->pdb,
696251881Speter                                    &db->skip,
697251881Speter                                    &db->skip_children,
698251881Speter                                    db->path,
699251881Speter                                    NULL,
700251881Speter                                    db->right_source,
701251881Speter                                    NULL /* copyfrom_source */,
702251881Speter                                    pb->pdb,
703251881Speter                                    eb->processor,
704251881Speter                                    db->pool, db->pool));
705251881Speter
706251881Speter  return SVN_NO_ERROR;
707251881Speter}
708251881Speter
709251881Speter/* An svn_delta_editor_t function.  */
710251881Speterstatic svn_error_t *
711251881Speteropen_directory(const char *path,
712251881Speter               void *parent_baton,
713251881Speter               svn_revnum_t base_revision,
714251881Speter               apr_pool_t *pool,
715251881Speter               void **child_baton)
716251881Speter{
717251881Speter  struct dir_baton *pb = parent_baton;
718251881Speter  struct edit_baton *eb = pb->edit_baton;
719251881Speter  struct dir_baton *db;
720251881Speter
721251881Speter  db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool);
722251881Speter
723251881Speter  *child_baton = db;
724251881Speter
725251881Speter  /* Process Skips. */
726251881Speter  if (pb->skip_children)
727251881Speter    {
728251881Speter      db->skip = TRUE;
729251881Speter      db->skip_children = TRUE;
730251881Speter      return SVN_NO_ERROR;
731251881Speter    }
732251881Speter
733251881Speter  db->left_source = svn_diff__source_create(eb->revision, db->pool);
734251881Speter  db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
735251881Speter
736251881Speter  SVN_ERR(eb->processor->dir_opened(&db->pdb,
737251881Speter                                    &db->skip, &db->skip_children,
738251881Speter                                    path,
739251881Speter                                    db->left_source,
740251881Speter                                    db->right_source,
741251881Speter                                    NULL /* copyfrom */,
742251881Speter                                    pb ? pb->pdb : NULL,
743251881Speter                                    eb->processor,
744251881Speter                                    db->pool, db->pool));
745251881Speter
746251881Speter  return SVN_NO_ERROR;
747251881Speter}
748251881Speter
749251881Speter
750251881Speter/* An svn_delta_editor_t function.  */
751251881Speterstatic svn_error_t *
752251881Speteradd_file(const char *path,
753251881Speter         void *parent_baton,
754251881Speter         const char *copyfrom_path,
755251881Speter         svn_revnum_t copyfrom_revision,
756251881Speter         apr_pool_t *pool,
757251881Speter         void **file_baton)
758251881Speter{
759251881Speter  struct dir_baton *pb = parent_baton;
760251881Speter  struct edit_baton *eb = pb->edit_baton;
761251881Speter  struct file_baton *fb;
762251881Speter
763251881Speter  /* ### TODO: support copyfrom? */
764251881Speter
765251881Speter  fb = make_file_baton(path, pb, TRUE, pb->pool);
766251881Speter  *file_baton = fb;
767251881Speter
768251881Speter  /* Process Skips. */
769251881Speter  if (pb->skip_children)
770251881Speter    {
771251881Speter      fb->skip = TRUE;
772251881Speter      return SVN_NO_ERROR;
773251881Speter    }
774251881Speter
775251881Speter  fb->pristine_props = pb->edit_baton->empty_hash;
776251881Speter
777251881Speter  fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
778251881Speter
779251881Speter  SVN_ERR(eb->processor->file_opened(&fb->pfb,
780251881Speter                                     &fb->skip,
781251881Speter                                     path,
782251881Speter                                     NULL,
783251881Speter                                     fb->right_source,
784251881Speter                                     NULL /* copy source */,
785251881Speter                                     pb->pdb,
786251881Speter                                     eb->processor,
787251881Speter                                     fb->pool, fb->pool));
788251881Speter
789251881Speter  return SVN_NO_ERROR;
790251881Speter}
791251881Speter
792251881Speter/* An svn_delta_editor_t function.  */
793251881Speterstatic svn_error_t *
794251881Speteropen_file(const char *path,
795251881Speter          void *parent_baton,
796251881Speter          svn_revnum_t base_revision,
797251881Speter          apr_pool_t *pool,
798251881Speter          void **file_baton)
799251881Speter{
800251881Speter  struct dir_baton *pb = parent_baton;
801251881Speter  struct file_baton *fb;
802251881Speter  struct edit_baton *eb = pb->edit_baton;
803251881Speter  fb = make_file_baton(path, pb, FALSE, pb->pool);
804251881Speter  *file_baton = fb;
805251881Speter
806251881Speter  /* Process Skips. */
807251881Speter  if (pb->skip_children)
808251881Speter    {
809251881Speter      fb->skip = TRUE;
810251881Speter      return SVN_NO_ERROR;
811251881Speter    }
812251881Speter
813251881Speter  fb->base_revision = base_revision;
814251881Speter
815251881Speter  fb->left_source = svn_diff__source_create(eb->revision, fb->pool);
816251881Speter  fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
817251881Speter
818251881Speter  SVN_ERR(eb->processor->file_opened(&fb->pfb,
819251881Speter                                     &fb->skip,
820251881Speter                                     path,
821251881Speter                                     fb->left_source,
822251881Speter                                     fb->right_source,
823251881Speter                                     NULL /* copy source */,
824251881Speter                                     pb->pdb,
825251881Speter                                     eb->processor,
826251881Speter                                     fb->pool, fb->pool));
827251881Speter
828251881Speter  return SVN_NO_ERROR;
829251881Speter}
830251881Speter
831251881Speter/* Do the work of applying the text delta.  */
832251881Speterstatic svn_error_t *
833251881Speterwindow_handler(svn_txdelta_window_t *window,
834251881Speter               void *window_baton)
835251881Speter{
836251881Speter  struct file_baton *fb = window_baton;
837251881Speter
838251881Speter  SVN_ERR(fb->apply_handler(window, fb->apply_baton));
839251881Speter
840251881Speter  if (!window)
841251881Speter    {
842251881Speter      fb->result_md5_checksum = svn_checksum__from_digest_md5(
843251881Speter                                        fb->result_digest,
844251881Speter                                        fb->pool);
845251881Speter    }
846251881Speter
847251881Speter  return SVN_NO_ERROR;
848251881Speter}
849251881Speter
850251881Speter/* Implements svn_stream_lazyopen_func_t. */
851251881Speterstatic svn_error_t *
852251881Speterlazy_open_source(svn_stream_t **stream,
853251881Speter                 void *baton,
854251881Speter                 apr_pool_t *result_pool,
855251881Speter                 apr_pool_t *scratch_pool)
856251881Speter{
857251881Speter  struct file_baton *fb = baton;
858251881Speter
859251881Speter  SVN_ERR(svn_stream_open_readonly(stream, fb->path_start_revision,
860251881Speter                                   result_pool, scratch_pool));
861251881Speter
862251881Speter  return SVN_NO_ERROR;
863251881Speter}
864251881Speter
865251881Speter/* Implements svn_stream_lazyopen_func_t. */
866251881Speterstatic svn_error_t *
867251881Speterlazy_open_result(svn_stream_t **stream,
868251881Speter                 void *baton,
869251881Speter                 apr_pool_t *result_pool,
870251881Speter                 apr_pool_t *scratch_pool)
871251881Speter{
872251881Speter  struct file_baton *fb = baton;
873251881Speter
874251881Speter  SVN_ERR(svn_stream_open_unique(stream, &fb->path_end_revision, NULL,
875251881Speter                                 svn_io_file_del_on_pool_cleanup,
876251881Speter                                 result_pool, scratch_pool));
877251881Speter
878251881Speter  return SVN_NO_ERROR;
879251881Speter}
880251881Speter
881251881Speter/* An svn_delta_editor_t function.  */
882251881Speterstatic svn_error_t *
883251881Speterapply_textdelta(void *file_baton,
884251881Speter                const char *base_md5_digest,
885251881Speter                apr_pool_t *pool,
886251881Speter                svn_txdelta_window_handler_t *handler,
887251881Speter                void **handler_baton)
888251881Speter{
889251881Speter  struct file_baton *fb = file_baton;
890251881Speter  svn_stream_t *src_stream;
891251881Speter  svn_stream_t *result_stream;
892251881Speter  apr_pool_t *scratch_pool = fb->pool;
893251881Speter
894251881Speter  /* Skip *everything* within a newly tree-conflicted directory. */
895251881Speter  if (fb->skip)
896251881Speter    {
897251881Speter      *handler = svn_delta_noop_window_handler;
898251881Speter      *handler_baton = NULL;
899251881Speter      return SVN_NO_ERROR;
900251881Speter    }
901251881Speter
902251881Speter  /* If we're not sending file text, then ignore any that we receive. */
903251881Speter  if (! fb->edit_baton->text_deltas)
904251881Speter    {
905251881Speter      /* Supply valid paths to indicate there is a text change. */
906251881Speter      SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_start_revision));
907251881Speter      SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_end_revision));
908251881Speter
909251881Speter      *handler = svn_delta_noop_window_handler;
910251881Speter      *handler_baton = NULL;
911251881Speter
912251881Speter      return SVN_NO_ERROR;
913251881Speter    }
914251881Speter
915251881Speter  /* We need the expected pristine file, so go get it */
916251881Speter  if (!fb->added)
917251881Speter    SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool));
918251881Speter  else
919251881Speter    SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision)));
920251881Speter
921251881Speter  SVN_ERR_ASSERT(fb->path_start_revision != NULL);
922251881Speter
923251881Speter  if (base_md5_digest != NULL)
924251881Speter    {
925251881Speter      svn_checksum_t *base_md5_checksum;
926251881Speter
927251881Speter      SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5,
928251881Speter                                     base_md5_digest, scratch_pool));
929251881Speter
930251881Speter      if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum))
931251881Speter        return svn_error_trace(svn_checksum_mismatch_err(
932251881Speter                                      base_md5_checksum,
933251881Speter                                      fb->start_md5_checksum,
934251881Speter                                      scratch_pool,
935251881Speter                                      _("Base checksum mismatch for '%s'"),
936251881Speter                                      fb->path));
937251881Speter    }
938251881Speter
939251881Speter  /* Open the file to be used as the base for second revision */
940251881Speter  src_stream = svn_stream_lazyopen_create(lazy_open_source, fb, TRUE,
941251881Speter                                          scratch_pool);
942251881Speter
943251881Speter  /* Open the file that will become the second revision after applying the
944251881Speter     text delta, it starts empty */
945251881Speter  result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE,
946251881Speter                                             scratch_pool);
947251881Speter
948251881Speter  svn_txdelta_apply(src_stream,
949251881Speter                    result_stream,
950251881Speter                    fb->result_digest,
951251881Speter                    fb->path, fb->pool,
952251881Speter                    &(fb->apply_handler), &(fb->apply_baton));
953251881Speter
954251881Speter  *handler = window_handler;
955251881Speter  *handler_baton = file_baton;
956251881Speter
957251881Speter  return SVN_NO_ERROR;
958251881Speter}
959251881Speter
960251881Speter/* An svn_delta_editor_t function.  When the file is closed we have a temporary
961251881Speter * file containing a pristine version of the repository file. This can
962251881Speter * be compared against the working copy.
963251881Speter *
964251881Speter * ### Ignore TEXT_CHECKSUM for now.  Someday we can use it to verify
965251881Speter * ### the integrity of the file being diffed.  Done efficiently, this
966251881Speter * ### would probably involve calculating the checksum as the data is
967251881Speter * ### received, storing the final checksum in the file_baton, and
968251881Speter * ### comparing against it here.
969251881Speter */
970251881Speterstatic svn_error_t *
971251881Speterclose_file(void *file_baton,
972251881Speter           const char *expected_md5_digest,
973251881Speter           apr_pool_t *pool)
974251881Speter{
975251881Speter  struct file_baton *fb = file_baton;
976251881Speter  struct dir_baton *pb = fb->parent_baton;
977251881Speter  struct edit_baton *eb = fb->edit_baton;
978251881Speter  apr_pool_t *scratch_pool;
979251881Speter
980251881Speter  /* Skip *everything* within a newly tree-conflicted directory. */
981251881Speter  if (fb->skip)
982251881Speter    {
983251881Speter      svn_pool_destroy(fb->pool);
984251881Speter      SVN_ERR(release_dir(pb));
985251881Speter      return SVN_NO_ERROR;
986251881Speter    }
987251881Speter
988251881Speter  scratch_pool = fb->pool;
989251881Speter
990251881Speter  if (expected_md5_digest && eb->text_deltas)
991251881Speter    {
992251881Speter      svn_checksum_t *expected_md5_checksum;
993251881Speter
994251881Speter      SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
995251881Speter                                     expected_md5_digest, scratch_pool));
996251881Speter
997251881Speter      if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum))
998251881Speter        return svn_error_trace(svn_checksum_mismatch_err(
999251881Speter                                      expected_md5_checksum,
1000251881Speter                                      fb->result_md5_checksum,
1001251881Speter                                      pool,
1002251881Speter                                      _("Checksum mismatch for '%s'"),
1003251881Speter                                      fb->path));
1004251881Speter    }
1005251881Speter
1006251881Speter  if (fb->added || fb->path_end_revision || fb->has_propchange)
1007251881Speter    {
1008251881Speter      apr_hash_t *right_props;
1009251881Speter
1010251881Speter      if (!fb->added && !fb->pristine_props)
1011251881Speter        {
1012251881Speter          /* We didn't receive a text change, so we have no pristine props.
1013251881Speter             Retrieve just the props now. */
1014251881Speter          SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool));
1015251881Speter        }
1016251881Speter
1017251881Speter      if (fb->pristine_props)
1018251881Speter        remove_non_prop_changes(fb->pristine_props, fb->propchanges);
1019251881Speter
1020251881Speter      right_props = svn_prop__patch(fb->pristine_props, fb->propchanges,
1021251881Speter                                    fb->pool);
1022251881Speter
1023251881Speter      if (fb->added)
1024251881Speter        SVN_ERR(eb->processor->file_added(fb->path,
1025251881Speter                                          NULL /* copyfrom_src */,
1026251881Speter                                          fb->right_source,
1027251881Speter                                          NULL /* copyfrom_file */,
1028251881Speter                                          fb->path_end_revision,
1029251881Speter                                          NULL /* copyfrom_props */,
1030251881Speter                                          right_props,
1031251881Speter                                          fb->pfb,
1032251881Speter                                          eb->processor,
1033251881Speter                                          fb->pool));
1034251881Speter      else
1035251881Speter        SVN_ERR(eb->processor->file_changed(fb->path,
1036251881Speter                                            fb->left_source,
1037251881Speter                                            fb->right_source,
1038251881Speter                                            fb->path_end_revision
1039251881Speter                                                    ? fb->path_start_revision
1040251881Speter                                                    : NULL,
1041251881Speter                                            fb->path_end_revision,
1042251881Speter                                            fb->pristine_props,
1043251881Speter                                            right_props,
1044251881Speter                                            (fb->path_end_revision != NULL),
1045251881Speter                                            fb->propchanges,
1046251881Speter                                            fb->pfb,
1047251881Speter                                            eb->processor,
1048251881Speter                                            fb->pool));
1049251881Speter    }
1050251881Speter
1051251881Speter  svn_pool_destroy(fb->pool); /* Destroy file and scratch pool */
1052251881Speter
1053251881Speter  SVN_ERR(release_dir(pb));
1054251881Speter
1055251881Speter  return SVN_NO_ERROR;
1056251881Speter}
1057251881Speter
1058251881Speter/* Report any accumulated prop changes via the 'dir_props_changed' callback,
1059251881Speter * and then call the 'dir_closed' callback.  Notify about any deleted paths
1060251881Speter * within this directory that have not already been notified, and then about
1061251881Speter * this directory itself (unless it was added, in which case the notification
1062251881Speter * was done at that time).
1063251881Speter *
1064251881Speter * An svn_delta_editor_t function.  */
1065251881Speterstatic svn_error_t *
1066251881Speterclose_directory(void *dir_baton,
1067251881Speter                apr_pool_t *pool)
1068251881Speter{
1069251881Speter  struct dir_baton *db = dir_baton;
1070251881Speter  struct edit_baton *eb = db->edit_baton;
1071251881Speter  apr_pool_t *scratch_pool;
1072251881Speter  apr_hash_t *pristine_props;
1073251881Speter  svn_boolean_t send_changed = FALSE;
1074251881Speter
1075251881Speter  scratch_pool = db->pool;
1076251881Speter
1077251881Speter  if ((db->has_propchange || db->added) && !db->skip)
1078251881Speter    {
1079251881Speter      if (db->added)
1080251881Speter        {
1081251881Speter          pristine_props = eb->empty_hash;
1082251881Speter        }
1083251881Speter      else
1084251881Speter        {
1085251881Speter          SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props,
1086251881Speter                                  db->path, db->base_revision, 0, scratch_pool));
1087251881Speter        }
1088251881Speter
1089251881Speter      if (db->propchanges->nelts > 0)
1090251881Speter        {
1091251881Speter          remove_non_prop_changes(pristine_props, db->propchanges);
1092251881Speter        }
1093251881Speter
1094251881Speter      if (db->propchanges->nelts > 0 || db->added)
1095251881Speter        {
1096251881Speter          apr_hash_t *right_props;
1097251881Speter
1098251881Speter          right_props = svn_prop__patch(pristine_props, db->propchanges,
1099251881Speter                                        scratch_pool);
1100251881Speter
1101251881Speter          if (db->added)
1102251881Speter            {
1103251881Speter              SVN_ERR(eb->processor->dir_added(db->path,
1104251881Speter                                           NULL /* copyfrom */,
1105251881Speter                                           db->right_source,
1106251881Speter                                           NULL /* copyfrom props */,
1107251881Speter                                           right_props,
1108251881Speter                                           db->pdb,
1109251881Speter                                           eb->processor,
1110251881Speter                                           db->pool));
1111251881Speter            }
1112251881Speter          else
1113251881Speter            {
1114251881Speter              SVN_ERR(eb->processor->dir_changed(db->path,
1115251881Speter                                                 db->left_source,
1116251881Speter                                                 db->right_source,
1117251881Speter                                                 pristine_props,
1118251881Speter                                                 right_props,
1119251881Speter                                                 db->propchanges,
1120251881Speter                                                 db->pdb,
1121251881Speter                                                 eb->processor,
1122251881Speter                                                 db->pool));
1123251881Speter            }
1124251881Speter
1125251881Speter          send_changed = TRUE; /* Skip dir_closed */
1126251881Speter        }
1127251881Speter    }
1128251881Speter
1129251881Speter  if (! db->skip && !send_changed)
1130251881Speter    {
1131251881Speter      SVN_ERR(eb->processor->dir_closed(db->path,
1132251881Speter                                        db->left_source,
1133251881Speter                                        db->right_source,
1134251881Speter                                        db->pdb,
1135251881Speter                                        eb->processor,
1136251881Speter                                        db->pool));
1137251881Speter    }
1138251881Speter  SVN_ERR(release_dir(db));
1139251881Speter
1140251881Speter  return SVN_NO_ERROR;
1141251881Speter}
1142251881Speter
1143251881Speter
1144251881Speter/* Record a prop change, which we will report later in close_file().
1145251881Speter *
1146251881Speter * An svn_delta_editor_t function.  */
1147251881Speterstatic svn_error_t *
1148251881Speterchange_file_prop(void *file_baton,
1149251881Speter                 const char *name,
1150251881Speter                 const svn_string_t *value,
1151251881Speter                 apr_pool_t *pool)
1152251881Speter{
1153251881Speter  struct file_baton *fb = file_baton;
1154251881Speter  svn_prop_t *propchange;
1155251881Speter  svn_prop_kind_t propkind;
1156251881Speter
1157251881Speter  /* Skip *everything* within a newly tree-conflicted directory. */
1158251881Speter  if (fb->skip)
1159251881Speter    return SVN_NO_ERROR;
1160251881Speter
1161251881Speter  propkind = svn_property_kind2(name);
1162251881Speter  if (propkind == svn_prop_wc_kind)
1163251881Speter    return SVN_NO_ERROR;
1164251881Speter  else if (propkind == svn_prop_regular_kind)
1165251881Speter    fb->has_propchange = TRUE;
1166251881Speter
1167251881Speter  propchange = apr_array_push(fb->propchanges);
1168251881Speter  propchange->name = apr_pstrdup(fb->pool, name);
1169299742Sdim  propchange->value = svn_string_dup(value, fb->pool);
1170251881Speter
1171251881Speter  return SVN_NO_ERROR;
1172251881Speter}
1173251881Speter
1174251881Speter/* Make a note of this prop change, to be reported when the dir is closed.
1175251881Speter *
1176251881Speter * An svn_delta_editor_t function.  */
1177251881Speterstatic svn_error_t *
1178251881Speterchange_dir_prop(void *dir_baton,
1179251881Speter                const char *name,
1180251881Speter                const svn_string_t *value,
1181251881Speter                apr_pool_t *pool)
1182251881Speter{
1183251881Speter  struct dir_baton *db = dir_baton;
1184251881Speter  svn_prop_t *propchange;
1185251881Speter  svn_prop_kind_t propkind;
1186251881Speter
1187251881Speter  /* Skip *everything* within a newly tree-conflicted directory. */
1188251881Speter  if (db->skip)
1189251881Speter    return SVN_NO_ERROR;
1190251881Speter
1191251881Speter  propkind = svn_property_kind2(name);
1192251881Speter  if (propkind == svn_prop_wc_kind)
1193251881Speter    return SVN_NO_ERROR;
1194251881Speter  else if (propkind == svn_prop_regular_kind)
1195251881Speter    db->has_propchange = TRUE;
1196251881Speter
1197251881Speter  propchange = apr_array_push(db->propchanges);
1198251881Speter  propchange->name = apr_pstrdup(db->pool, name);
1199299742Sdim  propchange->value = svn_string_dup(value, db->pool);
1200251881Speter
1201251881Speter  return SVN_NO_ERROR;
1202251881Speter}
1203251881Speter
1204251881Speter
1205251881Speter/* An svn_delta_editor_t function.  */
1206251881Speterstatic svn_error_t *
1207251881Speterclose_edit(void *edit_baton,
1208251881Speter           apr_pool_t *pool)
1209251881Speter{
1210251881Speter  struct edit_baton *eb = edit_baton;
1211251881Speter
1212251881Speter  svn_pool_destroy(eb->pool);
1213251881Speter
1214251881Speter  return SVN_NO_ERROR;
1215251881Speter}
1216251881Speter
1217251881Speter/* Notify that the node at PATH is 'missing'.
1218251881Speter * An svn_delta_editor_t function.  */
1219251881Speterstatic svn_error_t *
1220251881Speterabsent_directory(const char *path,
1221251881Speter                 void *parent_baton,
1222251881Speter                 apr_pool_t *pool)
1223251881Speter{
1224251881Speter  struct dir_baton *pb = parent_baton;
1225251881Speter  struct edit_baton *eb = pb->edit_baton;
1226251881Speter
1227251881Speter  SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1228251881Speter
1229251881Speter  return SVN_NO_ERROR;
1230251881Speter}
1231251881Speter
1232251881Speter
1233251881Speter/* Notify that the node at PATH is 'missing'.
1234251881Speter * An svn_delta_editor_t function.  */
1235251881Speterstatic svn_error_t *
1236251881Speterabsent_file(const char *path,
1237251881Speter            void *parent_baton,
1238251881Speter            apr_pool_t *pool)
1239251881Speter{
1240251881Speter  struct dir_baton *pb = parent_baton;
1241251881Speter  struct edit_baton *eb = pb->edit_baton;
1242251881Speter
1243251881Speter  SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1244251881Speter
1245251881Speter  return SVN_NO_ERROR;
1246251881Speter}
1247251881Speter
1248251881Speterstatic svn_error_t *
1249251881Speterfetch_kind_func(svn_node_kind_t *kind,
1250251881Speter                void *baton,
1251251881Speter                const char *path,
1252251881Speter                svn_revnum_t base_revision,
1253251881Speter                apr_pool_t *scratch_pool)
1254251881Speter{
1255251881Speter  struct edit_baton *eb = baton;
1256251881Speter
1257251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
1258251881Speter    base_revision = eb->revision;
1259251881Speter
1260251881Speter  SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1261251881Speter                            scratch_pool));
1262251881Speter
1263251881Speter  return SVN_NO_ERROR;
1264251881Speter}
1265251881Speter
1266251881Speterstatic svn_error_t *
1267251881Speterfetch_props_func(apr_hash_t **props,
1268251881Speter                 void *baton,
1269251881Speter                 const char *path,
1270251881Speter                 svn_revnum_t base_revision,
1271251881Speter                 apr_pool_t *result_pool,
1272251881Speter                 apr_pool_t *scratch_pool)
1273251881Speter{
1274251881Speter  struct edit_baton *eb = baton;
1275251881Speter  svn_node_kind_t node_kind;
1276251881Speter
1277251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
1278251881Speter    base_revision = eb->revision;
1279251881Speter
1280251881Speter  SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1281251881Speter                            scratch_pool));
1282251881Speter
1283251881Speter  if (node_kind == svn_node_file)
1284251881Speter    {
1285251881Speter      SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1286251881Speter                              NULL, NULL, props, result_pool));
1287251881Speter    }
1288251881Speter  else if (node_kind == svn_node_dir)
1289251881Speter    {
1290251881Speter      apr_array_header_t *tmp_props;
1291251881Speter
1292251881Speter      SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1293251881Speter                              base_revision, 0 /* Dirent fields */,
1294251881Speter                              result_pool));
1295251881Speter      tmp_props = svn_prop_hash_to_array(*props, result_pool);
1296251881Speter      SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1297251881Speter                                   result_pool));
1298251881Speter      *props = svn_prop_array_to_hash(tmp_props, result_pool);
1299251881Speter    }
1300251881Speter  else
1301251881Speter    {
1302251881Speter      *props = apr_hash_make(result_pool);
1303251881Speter    }
1304251881Speter
1305251881Speter  return SVN_NO_ERROR;
1306251881Speter}
1307251881Speter
1308251881Speterstatic svn_error_t *
1309251881Speterfetch_base_func(const char **filename,
1310251881Speter                void *baton,
1311251881Speter                const char *path,
1312251881Speter                svn_revnum_t base_revision,
1313251881Speter                apr_pool_t *result_pool,
1314251881Speter                apr_pool_t *scratch_pool)
1315251881Speter{
1316251881Speter  struct edit_baton *eb = baton;
1317251881Speter  svn_stream_t *fstream;
1318251881Speter  svn_error_t *err;
1319251881Speter
1320251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
1321251881Speter    base_revision = eb->revision;
1322251881Speter
1323251881Speter  SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1324251881Speter                                 svn_io_file_del_on_pool_cleanup,
1325251881Speter                                 result_pool, scratch_pool));
1326251881Speter
1327251881Speter  err = svn_ra_get_file(eb->ra_session, path, base_revision,
1328251881Speter                        fstream, NULL, NULL, scratch_pool);
1329251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1330251881Speter    {
1331251881Speter      svn_error_clear(err);
1332251881Speter      SVN_ERR(svn_stream_close(fstream));
1333251881Speter
1334251881Speter      *filename = NULL;
1335251881Speter      return SVN_NO_ERROR;
1336251881Speter    }
1337251881Speter  else if (err)
1338251881Speter    return svn_error_trace(err);
1339251881Speter
1340251881Speter  SVN_ERR(svn_stream_close(fstream));
1341251881Speter
1342251881Speter  return SVN_NO_ERROR;
1343251881Speter}
1344251881Speter
1345251881Speter/* Create a repository diff editor and baton.  */
1346251881Spetersvn_error_t *
1347251881Spetersvn_client__get_diff_editor2(const svn_delta_editor_t **editor,
1348251881Speter                             void **edit_baton,
1349251881Speter                             svn_ra_session_t *ra_session,
1350251881Speter                             svn_depth_t depth,
1351251881Speter                             svn_revnum_t revision,
1352251881Speter                             svn_boolean_t text_deltas,
1353251881Speter                             const svn_diff_tree_processor_t *processor,
1354251881Speter                             svn_cancel_func_t cancel_func,
1355251881Speter                             void *cancel_baton,
1356251881Speter                             apr_pool_t *result_pool)
1357251881Speter{
1358251881Speter  apr_pool_t *editor_pool = svn_pool_create(result_pool);
1359251881Speter  svn_delta_editor_t *tree_editor = svn_delta_default_editor(editor_pool);
1360251881Speter  struct edit_baton *eb = apr_pcalloc(editor_pool, sizeof(*eb));
1361251881Speter  svn_delta_shim_callbacks_t *shim_callbacks =
1362251881Speter                                svn_delta_shim_callbacks_default(editor_pool);
1363251881Speter
1364251881Speter  eb->pool = editor_pool;
1365251881Speter  eb->depth = depth;
1366251881Speter
1367251881Speter  eb->processor = processor;
1368251881Speter
1369251881Speter  eb->ra_session = ra_session;
1370251881Speter
1371251881Speter  eb->revision = revision;
1372299742Sdim  eb->target_revision = SVN_INVALID_REVNUM;
1373251881Speter  eb->empty_file = NULL;
1374251881Speter  eb->empty_hash = apr_hash_make(eb->pool);
1375251881Speter  eb->text_deltas = text_deltas;
1376251881Speter  eb->cancel_func = cancel_func;
1377251881Speter  eb->cancel_baton = cancel_baton;
1378251881Speter
1379251881Speter  tree_editor->set_target_revision = set_target_revision;
1380251881Speter  tree_editor->open_root = open_root;
1381251881Speter  tree_editor->delete_entry = delete_entry;
1382251881Speter  tree_editor->add_directory = add_directory;
1383251881Speter  tree_editor->open_directory = open_directory;
1384251881Speter  tree_editor->add_file = add_file;
1385251881Speter  tree_editor->open_file = open_file;
1386251881Speter  tree_editor->apply_textdelta = apply_textdelta;
1387251881Speter  tree_editor->close_file = close_file;
1388251881Speter  tree_editor->close_directory = close_directory;
1389251881Speter  tree_editor->change_file_prop = change_file_prop;
1390251881Speter  tree_editor->change_dir_prop = change_dir_prop;
1391251881Speter  tree_editor->close_edit = close_edit;
1392251881Speter  tree_editor->absent_directory = absent_directory;
1393251881Speter  tree_editor->absent_file = absent_file;
1394251881Speter
1395251881Speter  SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1396251881Speter                                            tree_editor, eb,
1397251881Speter                                            editor, edit_baton,
1398251881Speter                                            eb->pool));
1399251881Speter
1400251881Speter  shim_callbacks->fetch_kind_func = fetch_kind_func;
1401251881Speter  shim_callbacks->fetch_props_func = fetch_props_func;
1402251881Speter  shim_callbacks->fetch_base_func = fetch_base_func;
1403251881Speter  shim_callbacks->fetch_baton = eb;
1404251881Speter
1405251881Speter  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1406251881Speter                                   NULL, NULL, shim_callbacks,
1407251881Speter                                   result_pool, result_pool));
1408251881Speter
1409251881Speter  return SVN_NO_ERROR;
1410251881Speter}
1411