1251881Speter/*
2251881Speter * reporter.c : `reporter' vtable routines for updates.
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#include "svn_dirent_uri.h"
25251881Speter#include "svn_hash.h"
26251881Speter#include "svn_path.h"
27251881Speter#include "svn_types.h"
28251881Speter#include "svn_error.h"
29251881Speter#include "svn_error_codes.h"
30251881Speter#include "svn_fs.h"
31251881Speter#include "svn_repos.h"
32251881Speter#include "svn_pools.h"
33251881Speter#include "svn_props.h"
34251881Speter#include "repos.h"
35251881Speter#include "svn_private_config.h"
36251881Speter
37251881Speter#include "private/svn_dep_compat.h"
38251881Speter#include "private/svn_fspath.h"
39251881Speter#include "private/svn_subr_private.h"
40251881Speter#include "private/svn_string_private.h"
41251881Speter
42251881Speter#define NUM_CACHED_SOURCE_ROOTS 4
43251881Speter
44251881Speter/* Theory of operation: we write report operations out to a spill-buffer
45251881Speter   as we receive them.  When the report is finished, we read the
46251881Speter   operations back out again, using them to guide the progression of
47251881Speter   the delta between the source and target revs.
48251881Speter
49251881Speter   Spill-buffer content format: we use a simple ad-hoc format to store the
50251881Speter   report operations.  Each report operation is the concatention of
51251881Speter   the following ("+/-" indicates the single character '+' or '-';
52251881Speter   <length> and <revnum> are written out as decimal strings):
53251881Speter
54251881Speter     +/-                      '-' marks the end of the report
55251881Speter     If previous is +:
56251881Speter       <length>:<bytes>       Length-counted path string
57251881Speter       +/-                    '+' indicates the presence of link_path
58251881Speter       If previous is +:
59251881Speter         <length>:<bytes>     Length-counted link_path string
60251881Speter       +/-                    '+' indicates presence of revnum
61251881Speter       If previous is +:
62251881Speter         <revnum>:            Revnum of set_path or link_path
63251881Speter       +/-                    '+' indicates depth other than svn_depth_infinity
64251881Speter       If previous is +:
65251881Speter         <depth>:             "X","E","F","M" =>
66251881Speter                                 svn_depth_{exclude,empty,files,immediates}
67251881Speter       +/-                    '+' indicates start_empty field set
68251881Speter       +/-                    '+' indicates presence of lock_token field.
69251881Speter       If previous is +:
70251881Speter         <length>:<bytes>     Length-counted lock_token string
71251881Speter
72251881Speter   Terminology: for brevity, this file frequently uses the prefixes
73251881Speter   "s_" for source, "t_" for target, and "e_" for editor.  Also, to
74251881Speter   avoid overloading the word "target", we talk about the source
75251881Speter   "anchor and operand", rather than the usual "anchor and target". */
76251881Speter
77251881Speter/* Describes the state of a working copy subtree, as given by a
78251881Speter   report.  Because we keep a lookahead pathinfo, we need to allocate
79251881Speter   each one of these things in a subpool of the report baton and free
80251881Speter   it when done. */
81251881Spetertypedef struct path_info_t
82251881Speter{
83251881Speter  const char *path;            /* path, munged to be anchor-relative */
84251881Speter  const char *link_path;       /* NULL for set_path or delete_path */
85251881Speter  svn_revnum_t rev;            /* SVN_INVALID_REVNUM for delete_path */
86251881Speter  svn_depth_t depth;           /* Depth of this path, meaningless for files */
87251881Speter  svn_boolean_t start_empty;   /* Meaningless for delete_path */
88251881Speter  const char *lock_token;      /* NULL if no token */
89251881Speter  apr_pool_t *pool;            /* Container pool */
90251881Speter} path_info_t;
91251881Speter
92251881Speter/* Describes the standard revision properties that are relevant for
93251881Speter   reports.  Since a particular revision will often show up more than
94251881Speter   once in the report, we cache these properties for the time of the
95251881Speter   report generation. */
96251881Spetertypedef struct revision_info_t
97251881Speter{
98251881Speter  svn_revnum_t rev;            /* revision number */
99251881Speter  svn_string_t* date;          /* revision timestamp */
100251881Speter  svn_string_t* author;        /* name of the revisions' author */
101251881Speter} revision_info_t;
102251881Speter
103251881Speter/* A structure used by the routines within the `reporter' vtable,
104251881Speter   driven by the client as it describes its working copy revisions. */
105251881Spetertypedef struct report_baton_t
106251881Speter{
107251881Speter  /* Parameters remembered from svn_repos_begin_report3 */
108251881Speter  svn_repos_t *repos;
109251881Speter  const char *fs_base;         /* fspath corresponding to wc anchor */
110251881Speter  const char *s_operand;       /* anchor-relative wc target (may be empty) */
111251881Speter  svn_revnum_t t_rev;          /* Revnum which the edit will bring the wc to */
112251881Speter  const char *t_path;          /* FS path the edit will bring the wc to */
113251881Speter  svn_boolean_t text_deltas;   /* Whether to report text deltas */
114251881Speter  apr_size_t zero_copy_limit;  /* Max item size that will be sent using
115251881Speter                                  the zero-copy code path. */
116251881Speter
117251881Speter  /* If the client requested a specific depth, record it here; if the
118251881Speter     client did not, then this is svn_depth_unknown, and the depth of
119251881Speter     information transmitted from server to client will be governed
120251881Speter     strictly by the path-associated depths recorded in the report. */
121251881Speter  svn_depth_t requested_depth;
122251881Speter
123251881Speter  svn_boolean_t ignore_ancestry;
124251881Speter  svn_boolean_t send_copyfrom_args;
125251881Speter  svn_boolean_t is_switch;
126251881Speter  const svn_delta_editor_t *editor;
127251881Speter  void *edit_baton;
128251881Speter  svn_repos_authz_func_t authz_read_func;
129251881Speter  void *authz_read_baton;
130251881Speter
131251881Speter  /* The spill-buffer holding the report. */
132251881Speter  svn_spillbuf_reader_t *reader;
133251881Speter
134251881Speter  /* For the actual editor drive, we'll need a lookahead path info
135251881Speter     entry, a cache of FS roots, and a pool to store them. */
136251881Speter  path_info_t *lookahead;
137251881Speter  svn_fs_root_t *t_root;
138251881Speter  svn_fs_root_t *s_roots[NUM_CACHED_SOURCE_ROOTS];
139251881Speter
140251881Speter  /* Cache for revision properties. This is used to eliminate redundant
141251881Speter     revprop fetching. */
142251881Speter  apr_hash_t *revision_infos;
143251881Speter
144251881Speter  /* This will not change. So, fetch it once and reuse it. */
145251881Speter  svn_string_t *repos_uuid;
146251881Speter  apr_pool_t *pool;
147251881Speter} report_baton_t;
148251881Speter
149251881Speter/* The type of a function that accepts changes to an object's property
150251881Speter   list.  OBJECT is the object whose properties are being changed.
151251881Speter   NAME is the name of the property to change.  VALUE is the new value
152251881Speter   for the property, or zero if the property should be deleted. */
153251881Spetertypedef svn_error_t *proplist_change_fn_t(report_baton_t *b, void *object,
154251881Speter                                          const char *name,
155251881Speter                                          const svn_string_t *value,
156251881Speter                                          apr_pool_t *pool);
157251881Speter
158251881Speterstatic svn_error_t *delta_dirs(report_baton_t *b, svn_revnum_t s_rev,
159251881Speter                               const char *s_path, const char *t_path,
160251881Speter                               void *dir_baton, const char *e_path,
161251881Speter                               svn_boolean_t start_empty,
162251881Speter                               svn_depth_t wc_depth,
163251881Speter                               svn_depth_t requested_depth,
164251881Speter                               apr_pool_t *pool);
165251881Speter
166251881Speter/* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */
167251881Speter
168251881Speterstatic svn_error_t *
169251881Speterread_number(apr_uint64_t *num, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
170251881Speter{
171251881Speter  char c;
172251881Speter
173251881Speter  *num = 0;
174251881Speter  while (1)
175251881Speter    {
176251881Speter      SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
177251881Speter      if (c == ':')
178251881Speter        break;
179251881Speter      *num = *num * 10 + (c - '0');
180251881Speter    }
181251881Speter  return SVN_NO_ERROR;
182251881Speter}
183251881Speter
184251881Speterstatic svn_error_t *
185251881Speterread_string(const char **str, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
186251881Speter{
187251881Speter  apr_uint64_t len;
188251881Speter  apr_size_t size;
189251881Speter  apr_size_t amt;
190251881Speter  char *buf;
191251881Speter
192251881Speter  SVN_ERR(read_number(&len, reader, pool));
193251881Speter
194251881Speter  /* Len can never be less than zero.  But could len be so large that
195251881Speter     len + 1 wraps around and we end up passing 0 to apr_palloc(),
196251881Speter     thus getting a pointer to no storage?  Probably not (16 exabyte
197251881Speter     string, anyone?) but let's be future-proof anyway. */
198251881Speter  if (len + 1 < len || len + 1 > APR_SIZE_MAX)
199251881Speter    {
200251881Speter      /* xgettext doesn't expand preprocessor definitions, so we must
201251881Speter         pass translatable string to apr_psprintf() function to create
202251881Speter         intermediate string with appropriate format specifier. */
203251881Speter      return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
204251881Speter                               apr_psprintf(pool,
205251881Speter                                            _("Invalid length (%%%s) when "
206251881Speter                                              "about to read a string"),
207251881Speter                                            APR_UINT64_T_FMT),
208251881Speter                               len);
209251881Speter    }
210251881Speter
211251881Speter  size = (apr_size_t)len;
212251881Speter  buf = apr_palloc(pool, size+1);
213251881Speter  if (size > 0)
214251881Speter    {
215251881Speter      SVN_ERR(svn_spillbuf__reader_read(&amt, reader, buf, size, pool));
216251881Speter      SVN_ERR_ASSERT(amt == size);
217251881Speter    }
218251881Speter  buf[len] = 0;
219251881Speter  *str = buf;
220251881Speter  return SVN_NO_ERROR;
221251881Speter}
222251881Speter
223251881Speterstatic svn_error_t *
224251881Speterread_rev(svn_revnum_t *rev, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
225251881Speter{
226251881Speter  char c;
227251881Speter  apr_uint64_t num;
228251881Speter
229251881Speter  SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
230251881Speter  if (c == '+')
231251881Speter    {
232251881Speter      SVN_ERR(read_number(&num, reader, pool));
233251881Speter      *rev = (svn_revnum_t) num;
234251881Speter    }
235251881Speter  else
236251881Speter    *rev = SVN_INVALID_REVNUM;
237251881Speter  return SVN_NO_ERROR;
238251881Speter}
239251881Speter
240251881Speter/* Read a single character to set *DEPTH (having already read '+')
241251881Speter   from READER.  PATH is the path to which the depth applies, and is
242251881Speter   used for error reporting only. */
243251881Speterstatic svn_error_t *
244251881Speterread_depth(svn_depth_t *depth, svn_spillbuf_reader_t *reader, const char *path,
245251881Speter           apr_pool_t *pool)
246251881Speter{
247251881Speter  char c;
248251881Speter
249251881Speter  SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
250251881Speter  switch (c)
251251881Speter    {
252251881Speter    case 'X':
253251881Speter      *depth = svn_depth_exclude;
254251881Speter      break;
255251881Speter    case 'E':
256251881Speter      *depth = svn_depth_empty;
257251881Speter      break;
258251881Speter    case 'F':
259251881Speter      *depth = svn_depth_files;
260251881Speter      break;
261251881Speter    case 'M':
262251881Speter      *depth = svn_depth_immediates;
263251881Speter      break;
264251881Speter
265251881Speter      /* Note that we do not tolerate explicit representation of
266251881Speter         svn_depth_infinity here, because that's not how
267251881Speter         write_path_info() writes it. */
268251881Speter    default:
269251881Speter      return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
270251881Speter                               _("Invalid depth (%c) for path '%s'"), c, path);
271251881Speter    }
272251881Speter
273251881Speter  return SVN_NO_ERROR;
274251881Speter}
275251881Speter
276251881Speter/* Read a report operation *PI out of READER.  Set *PI to NULL if we
277251881Speter   have reached the end of the report. */
278251881Speterstatic svn_error_t *
279251881Speterread_path_info(path_info_t **pi,
280251881Speter               svn_spillbuf_reader_t *reader,
281251881Speter               apr_pool_t *pool)
282251881Speter{
283251881Speter  char c;
284251881Speter
285251881Speter  SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
286251881Speter  if (c == '-')
287251881Speter    {
288251881Speter      *pi = NULL;
289251881Speter      return SVN_NO_ERROR;
290251881Speter    }
291251881Speter
292251881Speter  *pi = apr_palloc(pool, sizeof(**pi));
293251881Speter  SVN_ERR(read_string(&(*pi)->path, reader, pool));
294251881Speter  SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
295251881Speter  if (c == '+')
296251881Speter    SVN_ERR(read_string(&(*pi)->link_path, reader, pool));
297251881Speter  else
298251881Speter    (*pi)->link_path = NULL;
299251881Speter  SVN_ERR(read_rev(&(*pi)->rev, reader, pool));
300251881Speter  SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
301251881Speter  if (c == '+')
302251881Speter    SVN_ERR(read_depth(&((*pi)->depth), reader, (*pi)->path, pool));
303251881Speter  else
304251881Speter    (*pi)->depth = svn_depth_infinity;
305251881Speter  SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
306251881Speter  (*pi)->start_empty = (c == '+');
307251881Speter  SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
308251881Speter  if (c == '+')
309251881Speter    SVN_ERR(read_string(&(*pi)->lock_token, reader, pool));
310251881Speter  else
311251881Speter    (*pi)->lock_token = NULL;
312251881Speter  (*pi)->pool = pool;
313251881Speter  return SVN_NO_ERROR;
314251881Speter}
315251881Speter
316251881Speter/* Return true if PI's path is a child of PREFIX (which has length PLEN). */
317251881Speterstatic svn_boolean_t
318251881Speterrelevant(path_info_t *pi, const char *prefix, apr_size_t plen)
319251881Speter{
320251881Speter  return (pi && strncmp(pi->path, prefix, plen) == 0 &&
321251881Speter          (!*prefix || pi->path[plen] == '/'));
322251881Speter}
323251881Speter
324251881Speter/* Fetch the next pathinfo from B->reader for a descendant of
325251881Speter   PREFIX.  If the next pathinfo is for an immediate child of PREFIX,
326251881Speter   set *ENTRY to the path component of the report information and
327251881Speter   *INFO to the path information for that entry.  If the next pathinfo
328251881Speter   is for a grandchild or other more remote descendant of PREFIX, set
329251881Speter   *ENTRY to the immediate child corresponding to that descendant and
330251881Speter   set *INFO to NULL.  If the next pathinfo is not for a descendant of
331251881Speter   PREFIX, or if we reach the end of the report, set both *ENTRY and
332251881Speter   *INFO to NULL.
333251881Speter
334251881Speter   At all times, B->lookahead is presumed to be the next pathinfo not
335251881Speter   yet returned as an immediate child, or NULL if we have reached the
336251881Speter   end of the report.  Because we use a lookahead element, we can't
337251881Speter   rely on the usual nested pool lifetimes, so allocate each pathinfo
338251881Speter   in a subpool of the report baton's pool.  The caller should delete
339251881Speter   (*INFO)->pool when it is done with the information. */
340251881Speterstatic svn_error_t *
341251881Speterfetch_path_info(report_baton_t *b, const char **entry, path_info_t **info,
342251881Speter                const char *prefix, apr_pool_t *pool)
343251881Speter{
344251881Speter  apr_size_t plen = strlen(prefix);
345251881Speter  const char *relpath, *sep;
346251881Speter  apr_pool_t *subpool;
347251881Speter
348251881Speter  if (!relevant(b->lookahead, prefix, plen))
349251881Speter    {
350251881Speter      /* No more entries relevant to prefix. */
351251881Speter      *entry = NULL;
352251881Speter      *info = NULL;
353251881Speter    }
354251881Speter  else
355251881Speter    {
356251881Speter      /* Take a look at the prefix-relative part of the path. */
357251881Speter      relpath = b->lookahead->path + (*prefix ? plen + 1 : 0);
358251881Speter      sep = strchr(relpath, '/');
359251881Speter      if (sep)
360251881Speter        {
361251881Speter          /* Return the immediate child part; do not advance. */
362251881Speter          *entry = apr_pstrmemdup(pool, relpath, sep - relpath);
363251881Speter          *info = NULL;
364251881Speter        }
365251881Speter      else
366251881Speter        {
367251881Speter          /* This is an immediate child; return it and advance. */
368251881Speter          *entry = relpath;
369251881Speter          *info = b->lookahead;
370251881Speter          subpool = svn_pool_create(b->pool);
371251881Speter          SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
372251881Speter        }
373251881Speter    }
374251881Speter  return SVN_NO_ERROR;
375251881Speter}
376251881Speter
377251881Speter/* Skip all path info entries relevant to *PREFIX.  Call this when the
378251881Speter   editor drive skips a directory. */
379251881Speterstatic svn_error_t *
380251881Speterskip_path_info(report_baton_t *b, const char *prefix)
381251881Speter{
382251881Speter  apr_size_t plen = strlen(prefix);
383251881Speter  apr_pool_t *subpool;
384251881Speter
385251881Speter  while (relevant(b->lookahead, prefix, plen))
386251881Speter    {
387251881Speter      svn_pool_destroy(b->lookahead->pool);
388251881Speter      subpool = svn_pool_create(b->pool);
389251881Speter      SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
390251881Speter    }
391251881Speter  return SVN_NO_ERROR;
392251881Speter}
393251881Speter
394251881Speter/* Return true if there is at least one path info entry relevant to *PREFIX. */
395251881Speterstatic svn_boolean_t
396251881Speterany_path_info(report_baton_t *b, const char *prefix)
397251881Speter{
398251881Speter  return relevant(b->lookahead, prefix, strlen(prefix));
399251881Speter}
400251881Speter
401251881Speter/* --- DRIVING THE EDITOR ONCE THE REPORT IS FINISHED --- */
402251881Speter
403251881Speter/* While driving the editor, the target root will remain constant, but
404251881Speter   we may have to jump around between source roots depending on the
405251881Speter   state of the working copy.  If we were to open a root each time we
406251881Speter   revisit a rev, we would get no benefit from node-id caching; on the
407251881Speter   other hand, if we hold open all the roots we ever visit, we'll use
408251881Speter   an unbounded amount of memory.  As a compromise, we maintain a
409251881Speter   fixed-size LRU cache of source roots.  get_source_root retrieves a
410251881Speter   root from the cache, using POOL to allocate the new root if
411251881Speter   necessary.  Be careful not to hold onto the root for too long,
412251881Speter   particularly after recursing, since another call to get_source_root
413251881Speter   can close it. */
414251881Speterstatic svn_error_t *
415251881Speterget_source_root(report_baton_t *b, svn_fs_root_t **s_root, svn_revnum_t rev)
416251881Speter{
417251881Speter  int i;
418251881Speter  svn_fs_root_t *root, *prev = NULL;
419251881Speter
420251881Speter  /* Look for the desired root in the cache, sliding all the unmatched
421251881Speter     entries backwards a slot to make room for the right one. */
422251881Speter  for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
423251881Speter    {
424251881Speter      root = b->s_roots[i];
425251881Speter      b->s_roots[i] = prev;
426251881Speter      if (root && svn_fs_revision_root_revision(root) == rev)
427251881Speter        break;
428251881Speter      prev = root;
429251881Speter    }
430251881Speter
431251881Speter  /* If we didn't find it, throw out the oldest root and open a new one. */
432251881Speter  if (i == NUM_CACHED_SOURCE_ROOTS)
433251881Speter    {
434251881Speter      if (prev)
435251881Speter        svn_fs_close_root(prev);
436251881Speter      SVN_ERR(svn_fs_revision_root(&root, b->repos->fs, rev, b->pool));
437251881Speter    }
438251881Speter
439251881Speter  /* Assign the desired root to the first cache slot and hand it back. */
440251881Speter  b->s_roots[0] = root;
441251881Speter  *s_root = root;
442251881Speter  return SVN_NO_ERROR;
443251881Speter}
444251881Speter
445251881Speter/* Call the directory property-setting function of B->editor to set
446251881Speter   the property NAME to VALUE on DIR_BATON. */
447251881Speterstatic svn_error_t *
448251881Speterchange_dir_prop(report_baton_t *b, void *dir_baton, const char *name,
449251881Speter                const svn_string_t *value, apr_pool_t *pool)
450251881Speter{
451251881Speter  return svn_error_trace(b->editor->change_dir_prop(dir_baton, name, value,
452251881Speter                                                    pool));
453251881Speter}
454251881Speter
455251881Speter/* Call the file property-setting function of B->editor to set the
456251881Speter   property NAME to VALUE on FILE_BATON. */
457251881Speterstatic svn_error_t *
458251881Speterchange_file_prop(report_baton_t *b, void *file_baton, const char *name,
459251881Speter                 const svn_string_t *value, apr_pool_t *pool)
460251881Speter{
461251881Speter  return svn_error_trace(b->editor->change_file_prop(file_baton, name, value,
462251881Speter                                                     pool));
463251881Speter}
464251881Speter
465251881Speter/* For the report B, return the relevant revprop data of revision REV in
466251881Speter   REVISION_INFO. The revision info will be allocated in b->pool.
467251881Speter   Temporaries get allocated on SCRATCH_POOL. */
468251881Speterstatic  svn_error_t *
469251881Speterget_revision_info(report_baton_t *b,
470251881Speter                  svn_revnum_t rev,
471251881Speter                  revision_info_t** revision_info,
472251881Speter                  apr_pool_t *scratch_pool)
473251881Speter{
474251881Speter  apr_hash_t *r_props;
475251881Speter  svn_string_t *cdate, *author;
476251881Speter  revision_info_t* info;
477251881Speter
478251881Speter  /* Try to find the info in the report's cache */
479251881Speter  info = apr_hash_get(b->revision_infos, &rev, sizeof(rev));
480251881Speter  if (!info)
481251881Speter    {
482251881Speter      /* Info is not available, yet.
483251881Speter         Get all revprops. */
484251881Speter      SVN_ERR(svn_fs_revision_proplist(&r_props,
485251881Speter                                       b->repos->fs,
486251881Speter                                       rev,
487251881Speter                                       scratch_pool));
488251881Speter
489251881Speter      /* Extract the committed-date. */
490251881Speter      cdate = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
491251881Speter
492251881Speter      /* Extract the last-author. */
493251881Speter      author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
494251881Speter
495251881Speter      /* Create a result object */
496251881Speter      info = apr_palloc(b->pool, sizeof(*info));
497251881Speter      info->rev = rev;
498251881Speter      info->date = cdate ? svn_string_dup(cdate, b->pool) : NULL;
499251881Speter      info->author = author ? svn_string_dup(author, b->pool) : NULL;
500251881Speter
501251881Speter      /* Cache it */
502251881Speter      apr_hash_set(b->revision_infos, &info->rev, sizeof(rev), info);
503251881Speter    }
504251881Speter
505251881Speter  *revision_info = info;
506251881Speter  return SVN_NO_ERROR;
507251881Speter}
508251881Speter
509251881Speter
510251881Speter/* Generate the appropriate property editing calls to turn the
511251881Speter   properties of S_REV/S_PATH into those of B->t_root/T_PATH.  If
512251881Speter   S_PATH is NULL, this is an add, so assume the target starts with no
513251881Speter   properties.  Pass OBJECT on to the editor function wrapper
514251881Speter   CHANGE_FN. */
515251881Speterstatic svn_error_t *
516251881Speterdelta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
517251881Speter                const char *t_path, const char *lock_token,
518251881Speter                proplist_change_fn_t *change_fn,
519251881Speter                void *object, apr_pool_t *pool)
520251881Speter{
521251881Speter  svn_fs_root_t *s_root;
522251881Speter  apr_hash_t *s_props = NULL, *t_props;
523251881Speter  apr_array_header_t *prop_diffs;
524251881Speter  int i;
525251881Speter  svn_revnum_t crev;
526251881Speter  revision_info_t *revision_info;
527251881Speter  svn_boolean_t changed;
528251881Speter  const svn_prop_t *pc;
529251881Speter  svn_lock_t *lock;
530251881Speter  apr_hash_index_t *hi;
531251881Speter
532251881Speter  /* Fetch the created-rev and send entry props. */
533251881Speter  SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool));
534251881Speter  if (SVN_IS_VALID_REVNUM(crev))
535251881Speter    {
536251881Speter      /* convert committed-rev to  string */
537251881Speter      char buf[SVN_INT64_BUFFER_SIZE];
538251881Speter      svn_string_t cr_str;
539251881Speter      cr_str.data = buf;
540251881Speter      cr_str.len = svn__i64toa(buf, crev);
541251881Speter
542251881Speter      /* Transmit the committed-rev. */
543251881Speter      SVN_ERR(change_fn(b, object,
544251881Speter                        SVN_PROP_ENTRY_COMMITTED_REV, &cr_str, pool));
545251881Speter
546251881Speter      SVN_ERR(get_revision_info(b, crev, &revision_info, pool));
547251881Speter
548251881Speter      /* Transmit the committed-date. */
549251881Speter      if (revision_info->date || s_path)
550251881Speter        SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_COMMITTED_DATE,
551251881Speter                          revision_info->date, pool));
552251881Speter
553251881Speter      /* Transmit the last-author. */
554251881Speter      if (revision_info->author || s_path)
555251881Speter        SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LAST_AUTHOR,
556251881Speter                          revision_info->author, pool));
557251881Speter
558251881Speter      /* Transmit the UUID. */
559251881Speter      SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_UUID,
560251881Speter                        b->repos_uuid, pool));
561251881Speter    }
562251881Speter
563251881Speter  /* Update lock properties. */
564251881Speter  if (lock_token)
565251881Speter    {
566251881Speter      SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool));
567251881Speter
568251881Speter      /* Delete a defunct lock. */
569251881Speter      if (! lock || strcmp(lock_token, lock->token) != 0)
570251881Speter        SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LOCK_TOKEN,
571251881Speter                          NULL, pool));
572251881Speter    }
573251881Speter
574251881Speter  if (s_path)
575251881Speter    {
576251881Speter      SVN_ERR(get_source_root(b, &s_root, s_rev));
577251881Speter
578251881Speter      /* Is this deltification worth our time? */
579251881Speter      SVN_ERR(svn_fs_props_changed(&changed, b->t_root, t_path, s_root,
580251881Speter                                   s_path, pool));
581251881Speter      if (! changed)
582251881Speter        return SVN_NO_ERROR;
583251881Speter
584251881Speter      /* If so, go ahead and get the source path's properties. */
585251881Speter      SVN_ERR(svn_fs_node_proplist(&s_props, s_root, s_path, pool));
586251881Speter    }
587251881Speter
588251881Speter  /* Get the target path's properties */
589251881Speter  SVN_ERR(svn_fs_node_proplist(&t_props, b->t_root, t_path, pool));
590251881Speter
591251881Speter  if (s_props && apr_hash_count(s_props))
592251881Speter    {
593251881Speter      /* Now transmit the differences. */
594251881Speter      SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
595251881Speter      for (i = 0; i < prop_diffs->nelts; i++)
596251881Speter        {
597251881Speter          pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
598251881Speter          SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
599251881Speter        }
600251881Speter    }
601251881Speter  else if (apr_hash_count(t_props))
602251881Speter    {
603251881Speter      /* So source, i.e. all new.  Transmit all target props. */
604251881Speter      for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi))
605251881Speter        {
606251881Speter          const void *key;
607251881Speter          void *val;
608251881Speter
609251881Speter          apr_hash_this(hi, &key, NULL, &val);
610251881Speter          SVN_ERR(change_fn(b, object, key, val, pool));
611251881Speter        }
612251881Speter    }
613251881Speter
614251881Speter  return SVN_NO_ERROR;
615251881Speter}
616251881Speter
617251881Speter/* Baton type to be passed into send_zero_copy_delta.
618251881Speter */
619251881Spetertypedef struct zero_copy_baton_t
620251881Speter{
621251881Speter  /* don't process data larger than this limit */
622251881Speter  apr_size_t zero_copy_limit;
623251881Speter
624251881Speter  /* window handler and baton to send the data to */
625251881Speter  svn_txdelta_window_handler_t dhandler;
626251881Speter  void *dbaton;
627251881Speter
628251881Speter  /* return value: will be set to TRUE, if the data was processed. */
629251881Speter  svn_boolean_t zero_copy_succeeded;
630251881Speter} zero_copy_baton_t;
631251881Speter
632251881Speter/* Implement svn_fs_process_contents_func_t.  If LEN is smaller than the
633251881Speter * limit given in *BATON, send the CONTENTS as an delta windows to the
634251881Speter * handler given in BATON and set the ZERO_COPY_SUCCEEDED flag in that
635251881Speter * BATON.  Otherwise, reset it to FALSE.
636251881Speter * Use POOL for temporary allocations.
637251881Speter */
638251881Speterstatic svn_error_t *
639251881Spetersend_zero_copy_delta(const unsigned char *contents,
640251881Speter                     apr_size_t len,
641251881Speter                     void *baton,
642251881Speter                     apr_pool_t *pool)
643251881Speter{
644251881Speter  zero_copy_baton_t *zero_copy_baton = baton;
645251881Speter
646251881Speter  /* if the item is too large, the caller must revert to traditional
647251881Speter     streaming code. */
648251881Speter  if (len > zero_copy_baton->zero_copy_limit)
649251881Speter    {
650251881Speter      zero_copy_baton->zero_copy_succeeded = FALSE;
651251881Speter      return SVN_NO_ERROR;
652251881Speter    }
653251881Speter
654251881Speter  SVN_ERR(svn_txdelta_send_contents(contents, len,
655251881Speter                                    zero_copy_baton->dhandler,
656251881Speter                                    zero_copy_baton->dbaton, pool));
657251881Speter
658251881Speter  /* all fine now */
659251881Speter  zero_copy_baton->zero_copy_succeeded = TRUE;
660251881Speter  return SVN_NO_ERROR;
661251881Speter}
662251881Speter
663251881Speter
664251881Speter/* Make the appropriate edits on FILE_BATON to change its contents and
665251881Speter   properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
666251881Speter   possibly using LOCK_TOKEN to determine if the client's lock on the file
667251881Speter   is defunct. */
668251881Speterstatic svn_error_t *
669251881Speterdelta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
670251881Speter            const char *s_path, const char *t_path, const char *lock_token,
671251881Speter            apr_pool_t *pool)
672251881Speter{
673251881Speter  svn_boolean_t changed;
674251881Speter  svn_fs_root_t *s_root = NULL;
675251881Speter  svn_txdelta_stream_t *dstream = NULL;
676251881Speter  svn_checksum_t *s_checksum;
677251881Speter  const char *s_hex_digest = NULL;
678251881Speter  svn_txdelta_window_handler_t dhandler;
679251881Speter  void *dbaton;
680251881Speter
681251881Speter  /* Compare the files' property lists.  */
682251881Speter  SVN_ERR(delta_proplists(b, s_rev, s_path, t_path, lock_token,
683251881Speter                          change_file_prop, file_baton, pool));
684251881Speter
685251881Speter  if (s_path)
686251881Speter    {
687251881Speter      SVN_ERR(get_source_root(b, &s_root, s_rev));
688251881Speter
689251881Speter      /* We're not interested in the theoretical difference between "has
690251881Speter         contents which have not changed with respect to" and "has the same
691251881Speter         actual contents as" when sending text-deltas.  If we know the
692251881Speter         delta is an empty one, we avoiding sending it in either case. */
693251881Speter      SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
694251881Speter                                       s_root, s_path, pool));
695251881Speter
696251881Speter      if (!changed)
697251881Speter        return SVN_NO_ERROR;
698251881Speter
699251881Speter      SVN_ERR(svn_fs_file_checksum(&s_checksum, svn_checksum_md5, s_root,
700251881Speter                                   s_path, TRUE, pool));
701251881Speter      s_hex_digest = svn_checksum_to_cstring(s_checksum, pool);
702251881Speter    }
703251881Speter
704251881Speter  /* Send the delta stream if desired, or just a NULL window if not. */
705251881Speter  SVN_ERR(b->editor->apply_textdelta(file_baton, s_hex_digest, pool,
706251881Speter                                     &dhandler, &dbaton));
707251881Speter
708251881Speter  if (dhandler != svn_delta_noop_window_handler)
709251881Speter    {
710251881Speter      if (b->text_deltas)
711251881Speter        {
712251881Speter          /* if we send deltas against empty streams, we may use our
713251881Speter             zero-copy code. */
714251881Speter          if (b->zero_copy_limit > 0 && s_path == NULL)
715251881Speter            {
716251881Speter              zero_copy_baton_t baton;
717251881Speter              svn_boolean_t called = FALSE;
718251881Speter
719251881Speter              baton.zero_copy_limit = b->zero_copy_limit;
720251881Speter              baton.dhandler = dhandler;
721251881Speter              baton.dbaton = dbaton;
722251881Speter              baton.zero_copy_succeeded = FALSE;
723251881Speter              SVN_ERR(svn_fs_try_process_file_contents(&called,
724251881Speter                                                       b->t_root, t_path,
725251881Speter                                                       send_zero_copy_delta,
726251881Speter                                                       &baton, pool));
727251881Speter
728251881Speter              /* data has been available and small enough,
729251881Speter                 i.e. been processed? */
730251881Speter              if (called && baton.zero_copy_succeeded)
731251881Speter                return SVN_NO_ERROR;
732251881Speter            }
733251881Speter
734251881Speter          SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path,
735251881Speter                                               b->t_root, t_path, pool));
736251881Speter          SVN_ERR(svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool));
737251881Speter        }
738251881Speter      else
739251881Speter        SVN_ERR(dhandler(NULL, dbaton));
740251881Speter    }
741251881Speter
742251881Speter  return SVN_NO_ERROR;
743251881Speter}
744251881Speter
745251881Speter/* Determine if the user is authorized to view B->t_root/PATH. */
746251881Speterstatic svn_error_t *
747251881Spetercheck_auth(report_baton_t *b, svn_boolean_t *allowed, const char *path,
748251881Speter           apr_pool_t *pool)
749251881Speter{
750251881Speter  if (b->authz_read_func)
751251881Speter    return svn_error_trace(b->authz_read_func(allowed, b->t_root, path,
752251881Speter                                              b->authz_read_baton, pool));
753251881Speter  *allowed = TRUE;
754251881Speter  return SVN_NO_ERROR;
755251881Speter}
756251881Speter
757251881Speter/* Create a dirent in *ENTRY for the given ROOT and PATH.  We use this to
758251881Speter   replace the source or target dirent when a report pathinfo tells us to
759251881Speter   change paths or revisions. */
760251881Speterstatic svn_error_t *
761251881Speterfake_dirent(const svn_fs_dirent_t **entry, svn_fs_root_t *root,
762251881Speter            const char *path, apr_pool_t *pool)
763251881Speter{
764251881Speter  svn_node_kind_t kind;
765251881Speter  svn_fs_dirent_t *ent;
766251881Speter
767251881Speter  SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
768251881Speter  if (kind == svn_node_none)
769251881Speter    *entry = NULL;
770251881Speter  else
771251881Speter    {
772251881Speter      ent = apr_palloc(pool, sizeof(**entry));
773251881Speter      /* ### All callers should be updated to pass just one of these
774251881Speter             formats */
775251881Speter      ent->name = (*path == '/') ? svn_fspath__basename(path, pool)
776251881Speter                                 : svn_relpath_basename(path, pool);
777251881Speter      SVN_ERR(svn_fs_node_id(&ent->id, root, path, pool));
778251881Speter      ent->kind = kind;
779251881Speter      *entry = ent;
780251881Speter    }
781251881Speter  return SVN_NO_ERROR;
782251881Speter}
783251881Speter
784251881Speter
785251881Speter/* Given REQUESTED_DEPTH, WC_DEPTH and the current entry's KIND,
786251881Speter   determine whether we need to send the whole entry, not just deltas.
787251881Speter   Please refer to delta_dirs' docstring for an explanation of the
788251881Speter   conditionals below. */
789251881Speterstatic svn_boolean_t
790251881Speteris_depth_upgrade(svn_depth_t wc_depth,
791251881Speter                 svn_depth_t requested_depth,
792251881Speter                 svn_node_kind_t kind)
793251881Speter{
794251881Speter  if (requested_depth == svn_depth_unknown
795251881Speter      || requested_depth <= wc_depth
796251881Speter      || wc_depth == svn_depth_immediates)
797251881Speter    return FALSE;
798251881Speter
799251881Speter  if (kind == svn_node_file
800251881Speter      && wc_depth == svn_depth_files)
801251881Speter    return FALSE;
802251881Speter
803251881Speter  if (kind == svn_node_dir
804251881Speter      && wc_depth == svn_depth_empty
805251881Speter      && requested_depth == svn_depth_files)
806251881Speter    return FALSE;
807251881Speter
808251881Speter  return TRUE;
809251881Speter}
810251881Speter
811251881Speter
812251881Speter/* Call the B->editor's add_file() function to create PATH as a child
813251881Speter   of PARENT_BATON, returning a new baton in *NEW_FILE_BATON.
814251881Speter   However, make an attempt to send 'copyfrom' arguments if they're
815251881Speter   available, by examining the closest copy of the original file
816251881Speter   O_PATH within B->t_root.  If any copyfrom args are discovered,
817251881Speter   return those in *COPYFROM_PATH and *COPYFROM_REV;  otherwise leave
818251881Speter   those return args untouched. */
819251881Speterstatic svn_error_t *
820251881Speteradd_file_smartly(report_baton_t *b,
821251881Speter                 const char *path,
822251881Speter                 void *parent_baton,
823251881Speter                 const char *o_path,
824251881Speter                 void **new_file_baton,
825251881Speter                 const char **copyfrom_path,
826251881Speter                 svn_revnum_t *copyfrom_rev,
827251881Speter                 apr_pool_t *pool)
828251881Speter{
829251881Speter  /* ### TODO:  use a subpool to do this work, clear it at the end? */
830251881Speter  svn_fs_t *fs = svn_repos_fs(b->repos);
831251881Speter  svn_fs_root_t *closest_copy_root = NULL;
832251881Speter  const char *closest_copy_path = NULL;
833251881Speter
834251881Speter  /* Pre-emptively assume no copyfrom args exist. */
835251881Speter  *copyfrom_path = NULL;
836251881Speter  *copyfrom_rev = SVN_INVALID_REVNUM;
837251881Speter
838251881Speter  if (b->send_copyfrom_args)
839251881Speter    {
840251881Speter      /* Find the destination of the nearest 'copy event' which may have
841251881Speter         caused o_path@t_root to exist. svn_fs_closest_copy only returns paths
842251881Speter         starting with '/', so make sure o_path always starts with a '/'
843251881Speter         too. */
844251881Speter      if (*o_path != '/')
845251881Speter        o_path = apr_pstrcat(pool, "/", o_path, (char *)NULL);
846251881Speter
847251881Speter      SVN_ERR(svn_fs_closest_copy(&closest_copy_root, &closest_copy_path,
848251881Speter                                  b->t_root, o_path, pool));
849251881Speter      if (closest_copy_root != NULL)
850251881Speter        {
851251881Speter          /* If the destination of the copy event is the same path as
852251881Speter             o_path, then we've found something interesting that should
853251881Speter             have 'copyfrom' history. */
854251881Speter          if (strcmp(closest_copy_path, o_path) == 0)
855251881Speter            {
856251881Speter              SVN_ERR(svn_fs_copied_from(copyfrom_rev, copyfrom_path,
857251881Speter                                         closest_copy_root, closest_copy_path,
858251881Speter                                         pool));
859251881Speter              if (b->authz_read_func)
860251881Speter                {
861251881Speter                  svn_boolean_t allowed;
862251881Speter                  svn_fs_root_t *copyfrom_root;
863251881Speter                  SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
864251881Speter                                               *copyfrom_rev, pool));
865251881Speter                  SVN_ERR(b->authz_read_func(&allowed, copyfrom_root,
866251881Speter                                             *copyfrom_path, b->authz_read_baton,
867251881Speter                                             pool));
868251881Speter                  if (! allowed)
869251881Speter                    {
870251881Speter                      *copyfrom_path = NULL;
871251881Speter                      *copyfrom_rev = SVN_INVALID_REVNUM;
872251881Speter                    }
873251881Speter                }
874251881Speter            }
875251881Speter        }
876251881Speter    }
877251881Speter
878251881Speter  return svn_error_trace(b->editor->add_file(path, parent_baton,
879251881Speter                                             *copyfrom_path, *copyfrom_rev,
880251881Speter                                             pool, new_file_baton));
881251881Speter}
882251881Speter
883251881Speter
884251881Speter/* Emit a series of editing operations to transform a source entry to
885251881Speter   a target entry.
886251881Speter
887251881Speter   S_REV and S_PATH specify the source entry.  S_ENTRY contains the
888251881Speter   already-looked-up information about the node-revision existing at
889251881Speter   that location.  S_PATH and S_ENTRY may be NULL if the entry does
890251881Speter   not exist in the source.  S_PATH may be non-NULL and S_ENTRY may be
891251881Speter   NULL if the caller expects INFO to modify the source to an existing
892251881Speter   location.
893251881Speter
894251881Speter   B->t_root and T_PATH specify the target entry.  T_ENTRY contains
895251881Speter   the already-looked-up information about the node-revision existing
896251881Speter   at that location.  T_PATH and T_ENTRY may be NULL if the entry does
897251881Speter   not exist in the target.
898251881Speter
899251881Speter   DIR_BATON and E_PATH contain the parameters which should be passed
900251881Speter   to the editor calls--DIR_BATON for the parent directory baton and
901251881Speter   E_PATH for the pathname.  (E_PATH is the anchor-relative working
902251881Speter   copy pathname, which may differ from the source and target
903251881Speter   pathnames if the report contains a link_path.)
904251881Speter
905251881Speter   INFO contains the report information for this working copy path, or
906251881Speter   NULL if there is none.  This function will internally modify the
907251881Speter   source and target entries as appropriate based on the report
908251881Speter   information.
909251881Speter
910251881Speter   WC_DEPTH and REQUESTED_DEPTH are propagated to delta_dirs() if
911251881Speter   necessary.  Refer to delta_dirs' docstring to find out what
912251881Speter   should happen for various combinations of WC_DEPTH/REQUESTED_DEPTH. */
913251881Speterstatic svn_error_t *
914251881Speterupdate_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
915251881Speter             const svn_fs_dirent_t *s_entry, const char *t_path,
916251881Speter             const svn_fs_dirent_t *t_entry, void *dir_baton,
917251881Speter             const char *e_path, path_info_t *info, svn_depth_t wc_depth,
918251881Speter             svn_depth_t requested_depth, apr_pool_t *pool)
919251881Speter{
920251881Speter  svn_fs_root_t *s_root;
921251881Speter  svn_boolean_t allowed, related;
922251881Speter  void *new_baton;
923251881Speter  svn_checksum_t *checksum;
924251881Speter  const char *hex_digest;
925251881Speter
926251881Speter  /* For non-switch operations, follow link_path in the target. */
927251881Speter  if (info && info->link_path && !b->is_switch)
928251881Speter    {
929251881Speter      t_path = info->link_path;
930251881Speter      SVN_ERR(fake_dirent(&t_entry, b->t_root, t_path, pool));
931251881Speter    }
932251881Speter
933251881Speter  if (info && !SVN_IS_VALID_REVNUM(info->rev))
934251881Speter    {
935251881Speter      /* Delete this entry in the source. */
936251881Speter      s_path = NULL;
937251881Speter      s_entry = NULL;
938251881Speter    }
939251881Speter  else if (info && s_path)
940251881Speter    {
941251881Speter      /* Follow the rev and possibly path in this entry. */
942251881Speter      s_path = (info->link_path) ? info->link_path : s_path;
943251881Speter      s_rev = info->rev;
944251881Speter      SVN_ERR(get_source_root(b, &s_root, s_rev));
945251881Speter      SVN_ERR(fake_dirent(&s_entry, s_root, s_path, pool));
946251881Speter    }
947251881Speter
948251881Speter  /* Don't let the report carry us somewhere nonexistent. */
949251881Speter  if (s_path && !s_entry)
950251881Speter    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
951251881Speter                             _("Working copy path '%s' does not exist in "
952251881Speter                               "repository"), e_path);
953251881Speter
954251881Speter  /* If the source and target both exist and are of the same kind,
955251881Speter     then find out whether they're related.  If they're exactly the
956251881Speter     same, then we don't have to do anything (unless the report has
957251881Speter     changes to the source).  If we're ignoring ancestry, then any two
958251881Speter     nodes of the same type are related enough for us. */
959251881Speter  related = FALSE;
960251881Speter  if (s_entry && t_entry && s_entry->kind == t_entry->kind)
961251881Speter    {
962251881Speter      int distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
963251881Speter      if (distance == 0 && !any_path_info(b, e_path)
964251881Speter          && (requested_depth <= wc_depth || t_entry->kind == svn_node_file))
965251881Speter        {
966251881Speter          if (!info)
967251881Speter            return SVN_NO_ERROR;
968251881Speter
969251881Speter          if (!info->start_empty)
970251881Speter            {
971251881Speter              svn_lock_t *lock;
972251881Speter
973251881Speter              if (!info->lock_token)
974251881Speter                return SVN_NO_ERROR;
975251881Speter
976251881Speter              SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool));
977251881Speter              if (lock && (strcmp(lock->token, info->lock_token) == 0))
978251881Speter                return SVN_NO_ERROR;
979251881Speter            }
980251881Speter        }
981251881Speter
982251881Speter      related = (distance != -1 || b->ignore_ancestry);
983251881Speter    }
984251881Speter
985251881Speter  /* If there's a source and it's not related to the target, nuke it. */
986251881Speter  if (s_entry && !related)
987251881Speter    {
988251881Speter      svn_revnum_t deleted_rev;
989251881Speter
990251881Speter      SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), t_path,
991251881Speter                                    s_rev, b->t_rev, &deleted_rev,
992251881Speter                                    pool));
993251881Speter
994251881Speter      if (!SVN_IS_VALID_REVNUM(deleted_rev))
995251881Speter        {
996251881Speter          /* Two possibilities: either the thing doesn't exist in S_REV; or
997251881Speter             it wasn't deleted between S_REV and B->T_REV.  In the first case,
998251881Speter             I think we should leave DELETED_REV as SVN_INVALID_REVNUM, but
999251881Speter             in the second, it should be set to B->T_REV-1 for the call to
1000251881Speter             delete_entry() below. */
1001251881Speter          svn_node_kind_t kind;
1002251881Speter
1003251881Speter          SVN_ERR(svn_fs_check_path(&kind, b->t_root, t_path, pool));
1004251881Speter          if (kind != svn_node_none)
1005251881Speter            deleted_rev = b->t_rev - 1;
1006251881Speter        }
1007251881Speter
1008251881Speter      SVN_ERR(b->editor->delete_entry(e_path, deleted_rev, dir_baton,
1009251881Speter                                      pool));
1010251881Speter      s_path = NULL;
1011251881Speter    }
1012251881Speter
1013251881Speter  /* If there's no target, we have nothing more to do. */
1014251881Speter  if (!t_entry)
1015251881Speter    return svn_error_trace(skip_path_info(b, e_path));
1016251881Speter
1017251881Speter  /* Check if the user is authorized to find out about the target. */
1018251881Speter  SVN_ERR(check_auth(b, &allowed, t_path, pool));
1019251881Speter  if (!allowed)
1020251881Speter    {
1021251881Speter      if (t_entry->kind == svn_node_dir)
1022251881Speter        SVN_ERR(b->editor->absent_directory(e_path, dir_baton, pool));
1023251881Speter      else
1024251881Speter        SVN_ERR(b->editor->absent_file(e_path, dir_baton, pool));
1025251881Speter      return svn_error_trace(skip_path_info(b, e_path));
1026251881Speter    }
1027251881Speter
1028251881Speter  if (t_entry->kind == svn_node_dir)
1029251881Speter    {
1030251881Speter      if (related)
1031251881Speter        SVN_ERR(b->editor->open_directory(e_path, dir_baton, s_rev, pool,
1032251881Speter                                          &new_baton));
1033251881Speter      else
1034251881Speter        SVN_ERR(b->editor->add_directory(e_path, dir_baton, NULL,
1035251881Speter                                         SVN_INVALID_REVNUM, pool,
1036251881Speter                                         &new_baton));
1037251881Speter
1038251881Speter      SVN_ERR(delta_dirs(b, s_rev, s_path, t_path, new_baton, e_path,
1039251881Speter                         info ? info->start_empty : FALSE,
1040251881Speter                         wc_depth, requested_depth, pool));
1041251881Speter      return svn_error_trace(b->editor->close_directory(new_baton, pool));
1042251881Speter    }
1043251881Speter  else
1044251881Speter    {
1045251881Speter      if (related)
1046251881Speter        {
1047251881Speter          SVN_ERR(b->editor->open_file(e_path, dir_baton, s_rev, pool,
1048251881Speter                                       &new_baton));
1049251881Speter          SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path,
1050251881Speter                              info ? info->lock_token : NULL, pool));
1051251881Speter        }
1052251881Speter      else
1053251881Speter        {
1054251881Speter          svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1055251881Speter          const char *copyfrom_path = NULL;
1056251881Speter          SVN_ERR(add_file_smartly(b, e_path, dir_baton, t_path, &new_baton,
1057251881Speter                                   &copyfrom_path, &copyfrom_rev, pool));
1058251881Speter          if (! copyfrom_path)
1059251881Speter            /* Send txdelta between empty file (s_path@s_rev doesn't
1060251881Speter               exist) and added file (t_path@t_root). */
1061251881Speter            SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path,
1062251881Speter                                info ? info->lock_token : NULL, pool));
1063251881Speter          else
1064251881Speter            /* Send txdelta between copied file (copyfrom_path@copyfrom_rev)
1065251881Speter               and added file (tpath@t_root). */
1066251881Speter            SVN_ERR(delta_files(b, new_baton, copyfrom_rev, copyfrom_path,
1067251881Speter                                t_path, info ? info->lock_token : NULL, pool));
1068251881Speter        }
1069251881Speter
1070251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, b->t_root,
1071251881Speter                                   t_path, TRUE, pool));
1072251881Speter      hex_digest = svn_checksum_to_cstring(checksum, pool);
1073251881Speter      return svn_error_trace(b->editor->close_file(new_baton, hex_digest,
1074251881Speter                                                   pool));
1075251881Speter    }
1076251881Speter}
1077251881Speter
1078251881Speter/* A helper macro for when we have to recurse into subdirectories. */
1079251881Speter#define DEPTH_BELOW_HERE(depth) ((depth) == svn_depth_immediates) ? \
1080251881Speter                                 svn_depth_empty : (depth)
1081251881Speter
1082251881Speter/* Emit edits within directory DIR_BATON (with corresponding path
1083251881Speter   E_PATH) with the changes from the directory S_REV/S_PATH to the
1084251881Speter   directory B->t_rev/T_PATH.  S_PATH may be NULL if the entry does
1085251881Speter   not exist in the source.
1086251881Speter
1087251881Speter   WC_DEPTH is this path's depth as reported by set_path/link_path.
1088251881Speter   REQUESTED_DEPTH is derived from the depth set by
1089251881Speter   svn_repos_begin_report().
1090251881Speter
1091251881Speter   When iterating over this directory's entries, the following tables
1092251881Speter   describe what happens for all possible combinations
1093251881Speter   of WC_DEPTH/REQUESTED_DEPTH (rows represent WC_DEPTH, columns
1094251881Speter   represent REQUESTED_DEPTH):
1095251881Speter
1096251881Speter   Legend:
1097251881Speter     X: ignore this entry (it's either below the requested depth, or
1098251881Speter        if the requested depth is svn_depth_unknown, below the working
1099251881Speter        copy depth)
1100251881Speter     o: handle this entry normally
1101251881Speter     U: handle the entry as if it were a newly added repository path
1102251881Speter        (the client is upgrading to a deeper wc and doesn't currently
1103251881Speter        have this entry, but it should be there after the upgrade, so we
1104251881Speter        need to send the whole thing, not just deltas)
1105251881Speter
1106251881Speter                              For files:
1107251881Speter   ______________________________________________________________
1108251881Speter   | req. depth| unknown | empty | files | immediates | infinity |
1109251881Speter   |wc. depth  |         |       |       |            |          |
1110251881Speter   |___________|_________|_______|_______|____________|__________|
1111251881Speter   |empty      |    X    |   X   |   U   |     U      |    U     |
1112251881Speter   |___________|_________|_______|_______|____________|__________|
1113251881Speter   |files      |    o    |   X   |   o   |     o      |    o     |
1114251881Speter   |___________|_________|_______|_______|____________|__________|
1115251881Speter   |immediates |    o    |   X   |   o   |     o      |    o     |
1116251881Speter   |___________|_________|_______|_______|____________|__________|
1117251881Speter   |infinity   |    o    |   X   |   o   |     o      |    o     |
1118251881Speter   |___________|_________|_______|_______|____________|__________|
1119251881Speter
1120251881Speter                            For directories:
1121251881Speter   ______________________________________________________________
1122251881Speter   | req. depth| unknown | empty | files | immediates | infinity |
1123251881Speter   |wc. depth  |         |       |       |            |          |
1124251881Speter   |___________|_________|_______|_______|____________|__________|
1125251881Speter   |empty      |    X    |   X   |   X   |     U      |    U     |
1126251881Speter   |___________|_________|_______|_______|____________|__________|
1127251881Speter   |files      |    X    |   X   |   X   |     U      |    U     |
1128251881Speter   |___________|_________|_______|_______|____________|__________|
1129251881Speter   |immediates |    o    |   X   |   X   |     o      |    o     |
1130251881Speter   |___________|_________|_______|_______|____________|__________|
1131251881Speter   |infinity   |    o    |   X   |   X   |     o      |    o     |
1132251881Speter   |___________|_________|_______|_______|____________|__________|
1133251881Speter
1134251881Speter   These rules are enforced by the is_depth_upgrade() function and by
1135251881Speter   various other checks below.
1136251881Speter*/
1137251881Speterstatic svn_error_t *
1138251881Speterdelta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
1139251881Speter           const char *t_path, void *dir_baton, const char *e_path,
1140251881Speter           svn_boolean_t start_empty, svn_depth_t wc_depth,
1141251881Speter           svn_depth_t requested_depth, apr_pool_t *pool)
1142251881Speter{
1143251881Speter  svn_fs_root_t *s_root;
1144251881Speter  apr_hash_t *s_entries = NULL, *t_entries;
1145251881Speter  apr_hash_index_t *hi;
1146262253Speter  apr_pool_t *subpool = svn_pool_create(pool);
1147262253Speter  apr_pool_t *iterpool;
1148251881Speter  const char *name, *s_fullpath, *t_fullpath, *e_fullpath;
1149251881Speter  path_info_t *info;
1150251881Speter
1151251881Speter  /* Compare the property lists.  If we're starting empty, pass a NULL
1152251881Speter     source path so that we add all the properties.
1153251881Speter
1154251881Speter     When we support directory locks, we must pass the lock token here. */
1155251881Speter  SVN_ERR(delta_proplists(b, s_rev, start_empty ? NULL : s_path, t_path,
1156262253Speter                          NULL, change_dir_prop, dir_baton, subpool));
1157262253Speter  svn_pool_clear(subpool);
1158251881Speter
1159251881Speter  if (requested_depth > svn_depth_empty
1160251881Speter      || requested_depth == svn_depth_unknown)
1161251881Speter    {
1162251881Speter      /* Get the list of entries in each of source and target. */
1163251881Speter      if (s_path && !start_empty)
1164251881Speter        {
1165251881Speter          SVN_ERR(get_source_root(b, &s_root, s_rev));
1166262253Speter          SVN_ERR(svn_fs_dir_entries(&s_entries, s_root, s_path, subpool));
1167251881Speter        }
1168262253Speter      SVN_ERR(svn_fs_dir_entries(&t_entries, b->t_root, t_path, subpool));
1169251881Speter
1170251881Speter      /* Iterate over the report information for this directory. */
1171262253Speter      iterpool = svn_pool_create(pool);
1172251881Speter
1173251881Speter      while (1)
1174251881Speter        {
1175251881Speter          const svn_fs_dirent_t *s_entry, *t_entry;
1176251881Speter
1177262253Speter          svn_pool_clear(iterpool);
1178262253Speter          SVN_ERR(fetch_path_info(b, &name, &info, e_path, iterpool));
1179251881Speter          if (!name)
1180251881Speter            break;
1181251881Speter
1182251881Speter          /* Invalid revnum means we should delete, unless this is
1183251881Speter             just an excluded subpath. */
1184251881Speter          if (info
1185251881Speter              && !SVN_IS_VALID_REVNUM(info->rev)
1186251881Speter              && info->depth != svn_depth_exclude)
1187251881Speter            {
1188251881Speter              /* We want to perform deletes before non-replacement adds,
1189251881Speter                 for graceful handling of case-only renames on
1190251881Speter                 case-insensitive client filesystems.  So, if the report
1191251881Speter                 item is a delete, remove the entry from the source hash,
1192251881Speter                 but don't update the entry yet. */
1193251881Speter              if (s_entries)
1194251881Speter                svn_hash_sets(s_entries, name, NULL);
1195251881Speter              continue;
1196251881Speter            }
1197251881Speter
1198262253Speter          e_fullpath = svn_relpath_join(e_path, name, iterpool);
1199262253Speter          t_fullpath = svn_fspath__join(t_path, name, iterpool);
1200251881Speter          t_entry = svn_hash_gets(t_entries, name);
1201262253Speter          s_fullpath = s_path ? svn_fspath__join(s_path, name, iterpool) : NULL;
1202251881Speter          s_entry = s_entries ?
1203251881Speter            svn_hash_gets(s_entries, name) : NULL;
1204251881Speter
1205251881Speter          /* The only special cases here are
1206251881Speter
1207251881Speter             - When requested_depth is files but the reported path is
1208251881Speter             a directory.  This is technically a client error, but we
1209251881Speter             handle it anyway, by skipping the entry.
1210251881Speter
1211251881Speter             - When the reported depth is svn_depth_exclude.
1212251881Speter          */
1213251881Speter          if ((! info || info->depth != svn_depth_exclude)
1214251881Speter              && (requested_depth != svn_depth_files
1215251881Speter                  || ((! t_entry || t_entry->kind != svn_node_dir)
1216251881Speter                      && (! s_entry || s_entry->kind != svn_node_dir))))
1217251881Speter            SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath,
1218251881Speter                                 t_entry, dir_baton, e_fullpath, info,
1219251881Speter                                 info ? info->depth
1220251881Speter                                      : DEPTH_BELOW_HERE(wc_depth),
1221262253Speter                                 DEPTH_BELOW_HERE(requested_depth), iterpool));
1222251881Speter
1223251881Speter          /* Don't revisit this name in the target or source entries. */
1224251881Speter          svn_hash_sets(t_entries, name, NULL);
1225251881Speter          if (s_entries
1226251881Speter              /* Keep the entry for later process if it is reported as
1227251881Speter                 excluded and got deleted in repos. */
1228251881Speter              && (! info || info->depth != svn_depth_exclude || t_entry))
1229251881Speter            svn_hash_sets(s_entries, name, NULL);
1230251881Speter
1231251881Speter          /* pathinfo entries live in their own subpools due to lookahead,
1232251881Speter             so we need to clear each one out as we finish with it. */
1233251881Speter          if (info)
1234251881Speter            svn_pool_destroy(info->pool);
1235251881Speter        }
1236251881Speter
1237251881Speter      /* Remove any deleted entries.  Do this before processing the
1238251881Speter         target, for graceful handling of case-only renames. */
1239251881Speter      if (s_entries)
1240251881Speter        {
1241262253Speter          for (hi = apr_hash_first(subpool, s_entries);
1242251881Speter               hi;
1243251881Speter               hi = apr_hash_next(hi))
1244251881Speter            {
1245251881Speter              const svn_fs_dirent_t *s_entry;
1246251881Speter
1247262253Speter              svn_pool_clear(iterpool);
1248251881Speter              s_entry = svn__apr_hash_index_val(hi);
1249251881Speter
1250251881Speter              if (svn_hash_gets(t_entries, s_entry->name) == NULL)
1251251881Speter                {
1252251881Speter                  svn_revnum_t deleted_rev;
1253251881Speter
1254251881Speter                  if (s_entry->kind == svn_node_file
1255251881Speter                      && wc_depth < svn_depth_files)
1256251881Speter                    continue;
1257251881Speter
1258251881Speter                  if (s_entry->kind == svn_node_dir
1259251881Speter                      && (wc_depth < svn_depth_immediates
1260251881Speter                          || requested_depth == svn_depth_files))
1261251881Speter                    continue;
1262251881Speter
1263251881Speter                  /* There is no corresponding target entry, so delete. */
1264262253Speter                  e_fullpath = svn_relpath_join(e_path, s_entry->name, iterpool);
1265251881Speter                  SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root),
1266251881Speter                                                svn_fspath__join(t_path,
1267251881Speter                                                                 s_entry->name,
1268262253Speter                                                                 iterpool),
1269251881Speter                                                s_rev, b->t_rev,
1270262253Speter                                                &deleted_rev, iterpool));
1271251881Speter
1272251881Speter                  SVN_ERR(b->editor->delete_entry(e_fullpath,
1273251881Speter                                                  deleted_rev,
1274262253Speter                                                  dir_baton, iterpool));
1275251881Speter                }
1276251881Speter            }
1277251881Speter        }
1278251881Speter
1279251881Speter      /* Loop over the dirents in the target. */
1280262253Speter      for (hi = apr_hash_first(subpool, t_entries);
1281262253Speter           hi;
1282262253Speter           hi = apr_hash_next(hi))
1283251881Speter        {
1284251881Speter          const svn_fs_dirent_t *s_entry, *t_entry;
1285251881Speter
1286262253Speter          svn_pool_clear(iterpool);
1287251881Speter          t_entry = svn__apr_hash_index_val(hi);
1288251881Speter
1289251881Speter          if (is_depth_upgrade(wc_depth, requested_depth, t_entry->kind))
1290251881Speter            {
1291251881Speter              /* We're making the working copy deeper, pretend the source
1292251881Speter                 doesn't exist. */
1293251881Speter              s_entry = NULL;
1294251881Speter              s_fullpath = NULL;
1295251881Speter            }
1296251881Speter          else
1297251881Speter            {
1298251881Speter              if (t_entry->kind == svn_node_file
1299251881Speter                  && requested_depth == svn_depth_unknown
1300251881Speter                  && wc_depth < svn_depth_files)
1301251881Speter                continue;
1302251881Speter
1303251881Speter              if (t_entry->kind == svn_node_dir
1304251881Speter                  && (wc_depth < svn_depth_immediates
1305251881Speter                      || requested_depth == svn_depth_files))
1306251881Speter                continue;
1307251881Speter
1308251881Speter              /* Look for an entry with the same name
1309251881Speter                 in the source dirents. */
1310251881Speter              s_entry = s_entries ?
1311251881Speter                  svn_hash_gets(s_entries, t_entry->name)
1312251881Speter                  : NULL;
1313251881Speter              s_fullpath = s_entry ?
1314262253Speter                  svn_fspath__join(s_path, t_entry->name, iterpool) : NULL;
1315251881Speter            }
1316251881Speter
1317251881Speter          /* Compose the report, editor, and target paths for this entry. */
1318262253Speter          e_fullpath = svn_relpath_join(e_path, t_entry->name, iterpool);
1319262253Speter          t_fullpath = svn_fspath__join(t_path, t_entry->name, iterpool);
1320251881Speter
1321251881Speter          SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath,
1322251881Speter                               t_entry, dir_baton, e_fullpath, NULL,
1323251881Speter                               DEPTH_BELOW_HERE(wc_depth),
1324251881Speter                               DEPTH_BELOW_HERE(requested_depth),
1325262253Speter                               iterpool));
1326251881Speter        }
1327251881Speter
1328251881Speter
1329251881Speter      /* Destroy iteration subpool. */
1330262253Speter      svn_pool_destroy(iterpool);
1331251881Speter    }
1332262253Speter
1333262253Speter  svn_pool_destroy(subpool);
1334262253Speter
1335251881Speter  return SVN_NO_ERROR;
1336251881Speter}
1337251881Speter
1338251881Speterstatic svn_error_t *
1339251881Speterdrive(report_baton_t *b, svn_revnum_t s_rev, path_info_t *info,
1340251881Speter      apr_pool_t *pool)
1341251881Speter{
1342251881Speter  const char *t_anchor, *s_fullpath;
1343251881Speter  svn_boolean_t allowed, info_is_set_path;
1344251881Speter  svn_fs_root_t *s_root;
1345251881Speter  const svn_fs_dirent_t *s_entry, *t_entry;
1346251881Speter  void *root_baton;
1347251881Speter
1348251881Speter  /* Compute the target path corresponding to the working copy anchor,
1349251881Speter     and check its authorization. */
1350251881Speter  t_anchor = *b->s_operand ? svn_fspath__dirname(b->t_path, pool) : b->t_path;
1351251881Speter  SVN_ERR(check_auth(b, &allowed, t_anchor, pool));
1352251881Speter  if (!allowed)
1353251881Speter    return svn_error_create
1354251881Speter      (SVN_ERR_AUTHZ_ROOT_UNREADABLE, NULL,
1355251881Speter       _("Not authorized to open root of edit operation"));
1356251881Speter
1357251881Speter  /* Collect information about the source and target nodes. */
1358251881Speter  s_fullpath = svn_fspath__join(b->fs_base, b->s_operand, pool);
1359251881Speter  SVN_ERR(get_source_root(b, &s_root, s_rev));
1360251881Speter  SVN_ERR(fake_dirent(&s_entry, s_root, s_fullpath, pool));
1361251881Speter  SVN_ERR(fake_dirent(&t_entry, b->t_root, b->t_path, pool));
1362251881Speter
1363251881Speter  /* If the operand is a locally added file or directory, it won't
1364251881Speter     exist in the source, so accept that. */
1365251881Speter  info_is_set_path = (SVN_IS_VALID_REVNUM(info->rev) && !info->link_path);
1366251881Speter  if (info_is_set_path && !s_entry)
1367251881Speter    s_fullpath = NULL;
1368251881Speter
1369251881Speter  /* Check if the target path exists first.  */
1370251881Speter  if (!*b->s_operand && !(t_entry))
1371251881Speter    return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
1372251881Speter                             _("Target path '%s' does not exist"),
1373251881Speter                             b->t_path);
1374251881Speter
1375251881Speter  /* If the anchor is the operand, the source and target must be dirs.
1376251881Speter     Check this before opening the root to avoid modifying the wc. */
1377251881Speter  else if (!*b->s_operand && (!s_entry || s_entry->kind != svn_node_dir
1378251881Speter                              || t_entry->kind != svn_node_dir))
1379251881Speter    return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, NULL,
1380251881Speter                            _("Cannot replace a directory from within"));
1381251881Speter
1382251881Speter  SVN_ERR(b->editor->set_target_revision(b->edit_baton, b->t_rev, pool));
1383251881Speter  SVN_ERR(b->editor->open_root(b->edit_baton, s_rev, pool, &root_baton));
1384251881Speter
1385251881Speter  /* If the anchor is the operand, diff the two directories; otherwise
1386251881Speter     update the operand within the anchor directory. */
1387251881Speter  if (!*b->s_operand)
1388251881Speter    SVN_ERR(delta_dirs(b, s_rev, s_fullpath, b->t_path, root_baton,
1389251881Speter                       "", info->start_empty, info->depth, b->requested_depth,
1390251881Speter                       pool));
1391251881Speter  else
1392251881Speter    SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, b->t_path,
1393251881Speter                         t_entry, root_baton, b->s_operand, info,
1394251881Speter                         info->depth, b->requested_depth, pool));
1395251881Speter
1396251881Speter  return svn_error_trace(b->editor->close_directory(root_baton, pool));
1397251881Speter}
1398251881Speter
1399251881Speter/* Initialize the baton fields for editor-driving, and drive the editor. */
1400251881Speterstatic svn_error_t *
1401251881Speterfinish_report(report_baton_t *b, apr_pool_t *pool)
1402251881Speter{
1403251881Speter  path_info_t *info;
1404251881Speter  apr_pool_t *subpool;
1405251881Speter  svn_revnum_t s_rev;
1406251881Speter  int i;
1407251881Speter
1408251881Speter  /* Save our pool to manage the lookahead and fs_root cache with. */
1409251881Speter  b->pool = pool;
1410251881Speter
1411251881Speter  /* Add the end marker. */
1412251881Speter  SVN_ERR(svn_spillbuf__reader_write(b->reader, "-", 1, pool));
1413251881Speter
1414251881Speter  /* Read the first pathinfo from the report and verify that it is a top-level
1415251881Speter     set_path entry. */
1416251881Speter  SVN_ERR(read_path_info(&info, b->reader, pool));
1417251881Speter  if (!info || strcmp(info->path, b->s_operand) != 0
1418251881Speter      || info->link_path || !SVN_IS_VALID_REVNUM(info->rev))
1419251881Speter    return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
1420251881Speter                            _("Invalid report for top level of working copy"));
1421251881Speter  s_rev = info->rev;
1422251881Speter
1423251881Speter  /* Initialize the lookahead pathinfo. */
1424251881Speter  subpool = svn_pool_create(pool);
1425251881Speter  SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
1426251881Speter
1427251881Speter  if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0)
1428251881Speter    {
1429251881Speter      /* If the operand of the wc operation is switched or deleted,
1430251881Speter         then info above is just a place-holder, and the only thing we
1431251881Speter         have to do is pass the revision it contains to open_root.
1432251881Speter         The next pathinfo actually describes the target. */
1433251881Speter      if (!*b->s_operand)
1434251881Speter        return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
1435251881Speter                                _("Two top-level reports with no target"));
1436251881Speter      /* If the client issued a set-path followed by a delete-path, we need
1437251881Speter         to respect the depth set by the initial set-path. */
1438251881Speter      if (! SVN_IS_VALID_REVNUM(b->lookahead->rev))
1439251881Speter        {
1440251881Speter          b->lookahead->depth = info->depth;
1441251881Speter        }
1442251881Speter      info = b->lookahead;
1443251881Speter      SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
1444251881Speter    }
1445251881Speter
1446251881Speter  /* Open the target root and initialize the source root cache. */
1447251881Speter  SVN_ERR(svn_fs_revision_root(&b->t_root, b->repos->fs, b->t_rev, pool));
1448251881Speter  for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
1449251881Speter    b->s_roots[i] = NULL;
1450251881Speter
1451251881Speter  {
1452251881Speter    svn_error_t *err = svn_error_trace(drive(b, s_rev, info, pool));
1453251881Speter
1454251881Speter    if (err == SVN_NO_ERROR)
1455251881Speter      return svn_error_trace(b->editor->close_edit(b->edit_baton, pool));
1456251881Speter
1457251881Speter    return svn_error_trace(
1458251881Speter                svn_error_compose_create(err,
1459251881Speter                                         b->editor->abort_edit(b->edit_baton,
1460251881Speter                                                               pool)));
1461251881Speter  }
1462251881Speter}
1463251881Speter
1464251881Speter/* --- COLLECTING THE REPORT INFORMATION --- */
1465251881Speter
1466251881Speter/* Record a report operation into the spill buffer.  Return an error
1467251881Speter   if DEPTH is svn_depth_unknown. */
1468251881Speterstatic svn_error_t *
1469251881Speterwrite_path_info(report_baton_t *b, const char *path, const char *lpath,
1470251881Speter                svn_revnum_t rev, svn_depth_t depth,
1471251881Speter                svn_boolean_t start_empty,
1472251881Speter                const char *lock_token, apr_pool_t *pool)
1473251881Speter{
1474251881Speter  const char *lrep, *rrep, *drep, *ltrep, *rep;
1475251881Speter
1476251881Speter  /* Munge the path to be anchor-relative, so that we can use edit paths
1477251881Speter     as report paths. */
1478251881Speter  path = svn_relpath_join(b->s_operand, path, pool);
1479251881Speter
1480251881Speter  lrep = lpath ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s",
1481251881Speter                              strlen(lpath), lpath) : "-";
1482251881Speter  rrep = (SVN_IS_VALID_REVNUM(rev)) ?
1483251881Speter    apr_psprintf(pool, "+%ld:", rev) : "-";
1484251881Speter
1485251881Speter  if (depth == svn_depth_exclude)
1486251881Speter    drep = "+X";
1487251881Speter  else if (depth == svn_depth_empty)
1488251881Speter    drep = "+E";
1489251881Speter  else if (depth == svn_depth_files)
1490251881Speter    drep = "+F";
1491251881Speter  else if (depth == svn_depth_immediates)
1492251881Speter    drep = "+M";
1493251881Speter  else if (depth == svn_depth_infinity)
1494251881Speter    drep = "-";
1495251881Speter  else
1496251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1497251881Speter                             _("Unsupported report depth '%s'"),
1498251881Speter                             svn_depth_to_word(depth));
1499251881Speter
1500251881Speter  ltrep = lock_token ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s",
1501251881Speter                                    strlen(lock_token), lock_token) : "-";
1502251881Speter  rep = apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s%s%s%s%c%s",
1503251881Speter                     strlen(path), path, lrep, rrep, drep,
1504251881Speter                     start_empty ? '+' : '-', ltrep);
1505251881Speter  return svn_error_trace(
1506251881Speter            svn_spillbuf__reader_write(b->reader, rep, strlen(rep), pool));
1507251881Speter}
1508251881Speter
1509251881Spetersvn_error_t *
1510251881Spetersvn_repos_set_path3(void *baton, const char *path, svn_revnum_t rev,
1511251881Speter                    svn_depth_t depth, svn_boolean_t start_empty,
1512251881Speter                    const char *lock_token, apr_pool_t *pool)
1513251881Speter{
1514251881Speter  return svn_error_trace(
1515251881Speter            write_path_info(baton, path, NULL, rev, depth, start_empty,
1516251881Speter                            lock_token, pool));
1517251881Speter}
1518251881Speter
1519251881Spetersvn_error_t *
1520251881Spetersvn_repos_link_path3(void *baton, const char *path, const char *link_path,
1521251881Speter                     svn_revnum_t rev, svn_depth_t depth,
1522251881Speter                     svn_boolean_t start_empty,
1523251881Speter                     const char *lock_token, apr_pool_t *pool)
1524251881Speter{
1525251881Speter  if (depth == svn_depth_exclude)
1526251881Speter    return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
1527251881Speter                            _("Depth 'exclude' not supported for link"));
1528251881Speter
1529251881Speter  return svn_error_trace(
1530251881Speter            write_path_info(baton, path, link_path, rev, depth,
1531251881Speter                            start_empty, lock_token, pool));
1532251881Speter}
1533251881Speter
1534251881Spetersvn_error_t *
1535251881Spetersvn_repos_delete_path(void *baton, const char *path, apr_pool_t *pool)
1536251881Speter{
1537251881Speter  /* We pass svn_depth_infinity because deletion of a path always
1538251881Speter     deletes everything underneath it. */
1539251881Speter  return svn_error_trace(
1540251881Speter            write_path_info(baton, path, NULL, SVN_INVALID_REVNUM,
1541251881Speter                            svn_depth_infinity, FALSE, NULL, pool));
1542251881Speter}
1543251881Speter
1544251881Spetersvn_error_t *
1545251881Spetersvn_repos_finish_report(void *baton, apr_pool_t *pool)
1546251881Speter{
1547251881Speter  report_baton_t *b = baton;
1548251881Speter
1549251881Speter  return svn_error_trace(finish_report(b, pool));
1550251881Speter}
1551251881Speter
1552251881Spetersvn_error_t *
1553251881Spetersvn_repos_abort_report(void *baton, apr_pool_t *pool)
1554251881Speter{
1555251881Speter  return SVN_NO_ERROR;
1556251881Speter}
1557251881Speter
1558251881Speter/* --- BEGINNING THE REPORT --- */
1559251881Speter
1560251881Speter
1561251881Spetersvn_error_t *
1562251881Spetersvn_repos_begin_report3(void **report_baton,
1563251881Speter                        svn_revnum_t revnum,
1564251881Speter                        svn_repos_t *repos,
1565251881Speter                        const char *fs_base,
1566251881Speter                        const char *s_operand,
1567251881Speter                        const char *switch_path,
1568251881Speter                        svn_boolean_t text_deltas,
1569251881Speter                        svn_depth_t depth,
1570251881Speter                        svn_boolean_t ignore_ancestry,
1571251881Speter                        svn_boolean_t send_copyfrom_args,
1572251881Speter                        const svn_delta_editor_t *editor,
1573251881Speter                        void *edit_baton,
1574251881Speter                        svn_repos_authz_func_t authz_read_func,
1575251881Speter                        void *authz_read_baton,
1576251881Speter                        apr_size_t zero_copy_limit,
1577251881Speter                        apr_pool_t *pool)
1578251881Speter{
1579251881Speter  report_baton_t *b;
1580251881Speter  const char *uuid;
1581251881Speter
1582251881Speter  if (depth == svn_depth_exclude)
1583251881Speter    return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
1584251881Speter                            _("Request depth 'exclude' not supported"));
1585251881Speter
1586251881Speter  SVN_ERR(svn_fs_get_uuid(repos->fs, &uuid, pool));
1587251881Speter
1588251881Speter  /* Build a reporter baton.  Copy strings in case the caller doesn't
1589251881Speter     keep track of them. */
1590251881Speter  b = apr_palloc(pool, sizeof(*b));
1591251881Speter  b->repos = repos;
1592251881Speter  b->fs_base = svn_fspath__canonicalize(fs_base, pool);
1593251881Speter  b->s_operand = apr_pstrdup(pool, s_operand);
1594251881Speter  b->t_rev = revnum;
1595251881Speter  b->t_path = switch_path ? svn_fspath__canonicalize(switch_path, pool)
1596251881Speter                          : svn_fspath__join(b->fs_base, s_operand, pool);
1597251881Speter  b->text_deltas = text_deltas;
1598251881Speter  b->zero_copy_limit = zero_copy_limit;
1599251881Speter  b->requested_depth = depth;
1600251881Speter  b->ignore_ancestry = ignore_ancestry;
1601251881Speter  b->send_copyfrom_args = send_copyfrom_args;
1602251881Speter  b->is_switch = (switch_path != NULL);
1603251881Speter  b->editor = editor;
1604251881Speter  b->edit_baton = edit_baton;
1605251881Speter  b->authz_read_func = authz_read_func;
1606251881Speter  b->authz_read_baton = authz_read_baton;
1607251881Speter  b->revision_infos = apr_hash_make(pool);
1608251881Speter  b->pool = pool;
1609251881Speter  b->reader = svn_spillbuf__reader_create(1000 /* blocksize */,
1610251881Speter                                          1000000 /* maxsize */,
1611251881Speter                                          pool);
1612251881Speter  b->repos_uuid = svn_string_create(uuid, pool);
1613251881Speter
1614251881Speter  /* Hand reporter back to client. */
1615251881Speter  *report_baton = b;
1616251881Speter  return SVN_NO_ERROR;
1617251881Speter}
1618