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